diff --git a/dev_tests/src/verify_expr.rs b/dev_tests/src/verify_expr.rs index 56168e3..978b618 100644 --- a/dev_tests/src/verify_expr.rs +++ b/dev_tests/src/verify_expr.rs @@ -16,43 +16,40 @@ use rottlib::parser::Parser; /// Keep these small: the goal is to inspect lexer diagnostics and delimiter /// recovery behavior, not full parser behavior. const TEST_CASES: &[(&str, &str)] = &[ - // P0031 - FunctionCallArgumentMissingComma + // P0027: `else` without a matching `if` + // + // `else` cannot start a standalone block item. The parser should report + // P0027 at `else`, then recover by bailing out of the current block. ( - "files/P0031_01.uc", - "{\n Func(A B);\n Log(\"after\");\n}\n", + "files/P0027_01.uc", + "{\n local bool bReady;\n bReady = CheckReady();\n else { StartMatch(); }\n NotifyReady();\n}\n", ), + + // P0027: `case` outside of a `switch` + // + // `case` is a switch-arm boundary, not a valid statement or expression + // starter in an ordinary block. ( - "files/P0031_02.uc", - "{\n Func\n (A 123);\n Log(\"after\");\n}\n", + "files/P0027_02.uc", + "{ local int Count; Count = 3; case 3: Count++; UpdateHud();}", ), + + // P0027: standalone `until` without a preceding `do` + // + // `until` is only meaningful as the tail of `do ... until`, so it should + // not be accepted as a normal block item. ( - "files/P0031_03.uc", - "{\n Func(\n A\n new SomeClass\n );\n Log(\"after\");\n}\n", + "files/P0027_03.uc", + "{\n local bool bDone;\n bDone = false;\n until (bDone)\n TickWork();\n}\n", ), - // P0032 - FunctionCallMissingClosingParenthesis - ("files/P0032_01.uc", "Func("), - ("files/P0032_02.uc", "Func\n(\n A,"), - ("files/P0032_03.uc", "Func(A,\n B,"), + + // P0027: preprocessor/exec directive inside a statement block + // + // `#exec` is declaration/top-level-like syntax, not a valid statement or + // expression inside a braced statement block. ( - "files/P0032_04.uc", - "{\n Func\n (\n A,\n B,\n", - ), - // P0033 - FunctionCallUnexpectedTokenInArgumentList - ( - "files/P0033_01.uc", - "{\n Func(A #, B);\n Log(\"after\");\n}\n", - ), - ( - "files/P0033_02.uc", - "{\n Func\n (A\n :,\n B);\n Log(\"after\");\n}\n", - ), - ( - "files/P0033_03.uc", - "{\n Func(\n A ?\n , B\n );\n Log(\"after\");\n}\n", - ), - ( - "files/P0033_04.uc", - "{\n Func\n (\n A\n #,\n B\n );\n Log(\"after\");\n}\n", + "files/P0027_04.uc", + "{\n local int Count;\n Count = 0;\n #exec TEXTURE IMPORT NAME=Bad FILE=Bad.bmp\n Count++;\n}\n", ), ]; @@ -138,4 +135,4 @@ fn render_diagnostic( _colors: bool, ) { diag.render(file, file_name.unwrap_or("")); -} +} \ No newline at end of file diff --git a/rottlib/src/ast/expressions.rs b/rottlib/src/ast/expressions.rs index ff1dfb5..d79cdd7 100644 --- a/rottlib/src/ast/expressions.rs +++ b/rottlib/src/ast/expressions.rs @@ -2,12 +2,11 @@ //! //! This module defines ordinary expressions together with expression-shaped //! control-flow and block forms parsed by the language. -use super::{ - IdentifierToken, InfixOperator, PostfixOperator, PrefixOperator, QualifiedIdentifierRef, - StatementRef, +use super::{IdentifierToken, InfixOperator, PostfixOperator, PrefixOperator, + QualifiedIdentifierRef, StatementRef, }; -use crate::arena::ArenaVec; use crate::lexer::TokenSpan; +use crate::arena::ArenaVec; use super::super::lexer::TokenPosition; @@ -84,7 +83,7 @@ pub enum Expression<'src, 'arena> { /// arguments in syntaxes that allow empty slots. Call { callee: ExpressionRef<'src, 'arena>, - arguments: ArgumentList<'src, 'arena>, + arguments: ArenaVec<'arena, Option>>, }, /// Prefix unary operator application: `op rhs`. PrefixUnary(PrefixOperator, ExpressionRef<'src, 'arena>), @@ -180,9 +179,6 @@ pub enum Expression<'src, 'arena> { Error, } -/// Arguments in any comma-separated list. -pub type ArgumentList<'src, 'arena> = ArenaVec<'arena, OptionalExpression<'src, 'arena>>; - /// Statements contained in a `{ ... }` block. pub type StatementList<'src, 'arena> = ArenaVec<'arena, StatementRef<'src, 'arena>>; diff --git a/rottlib/src/diagnostics/parse_error_diagnostics/mod.rs b/rottlib/src/diagnostics/parse_error_diagnostics/mod.rs index 32d7537..9e13ecd 100644 --- a/rottlib/src/diagnostics/parse_error_diagnostics/mod.rs +++ b/rottlib/src/diagnostics/parse_error_diagnostics/mod.rs @@ -20,7 +20,6 @@ use crate::parser::{ParseError, ParseErrorKind}; mod block_items; mod control_flow_expressions; mod primary_expressions; -mod selector_expressions; #[derive(Clone, Copy)] enum FoundAt<'src> { @@ -77,7 +76,6 @@ pub(crate) fn diagnostic_from_parse_error<'src>( ) -> Diagnostic { use control_flow_expressions::*; use primary_expressions::*; - use selector_expressions::*; match error.kind { // primary_expressions.rs ParseErrorKind::ParenthesizedExpressionInvalidStart => { @@ -148,23 +146,6 @@ pub(crate) fn diagnostic_from_parse_error<'src>( diagnostic_block_missing_closing_brace(error, file) } ParseErrorKind::BlockExpectedItem => diagnostic_block_expected_item(error, file), - // selector_expression.rs - ParseErrorKind::MemberAccessMissingMemberName => { - diagnostic_member_access_missing_member_name(error, file) - } - ParseErrorKind::IndexMissingExpression => diagnostic_index_missing_expression(error, file), - ParseErrorKind::IndexMissingClosingBracket => { - diagnostic_index_missing_closing_bracket(error, file) - } - ParseErrorKind::FunctionCallArgumentMissingComma => { - diagnostic_function_call_argument_missing_comma(error, file) - } - ParseErrorKind::FunctionCallMissingClosingParenthesis => { - diagnostic_function_call_missing_closing_parenthesis(error, file) - } - ParseErrorKind::FunctionCallUnexpectedTokenInArgumentList => { - diagnostic_function_call_unexpected_token_in_argument_list(error, file) - } _ => DiagnosticBuilder::error(format!("error {:?} while parsing", error.kind)) .primary_label(error.covered_span, "happened here") diff --git a/rottlib/src/diagnostics/parse_error_diagnostics/primary_expressions.rs b/rottlib/src/diagnostics/parse_error_diagnostics/primary_expressions.rs index ab4a28d..8c07233 100644 --- a/rottlib/src/diagnostics/parse_error_diagnostics/primary_expressions.rs +++ b/rottlib/src/diagnostics/parse_error_diagnostics/primary_expressions.rs @@ -167,9 +167,7 @@ pub(super) fn diagnostic_class_type_expected_qualified_type_name<'src>( let qualifier_dot_span = error.related_spans.get("qualifier_dot").copied(); let class_span = error.related_spans.get("class_keyword").copied(); - let blame_pos = error.blame_span.end; - - let (header_text, primary_text) = match found_at(file, blame_pos) { + let (header_text, primary_text) = match found_at(file, error.blame_span.end) { FoundAt::Token(token_text) => ( format!( "expected another type segment after `.`, found `{}`", @@ -190,7 +188,7 @@ pub(super) fn diagnostic_class_type_expected_qualified_type_name<'src>( let mut builder = DiagnosticBuilder::error(header_text); if let Some(dot_span) = qualifier_dot_span { - if !file.same_line(dot_span.start, blame_pos) { + if !file.same_line(dot_span.start, error.blame_span.end) { builder = builder.secondary_label( dot_span, "after this `.`, another type segment was expected", @@ -207,10 +205,7 @@ pub(super) fn diagnostic_class_type_expected_qualified_type_name<'src>( } } - let primary_span = TokenSpan { - start: blame_pos, - end: blame_pos, - }; + let primary_span = collapse_span_to_end_on_same_line(file, error.blame_span); builder .primary_label(primary_span, primary_text) diff --git a/rottlib/src/diagnostics/parse_error_diagnostics/selector_expressions.rs b/rottlib/src/diagnostics/parse_error_diagnostics/selector_expressions.rs deleted file mode 100644 index dd65d9a..0000000 --- a/rottlib/src/diagnostics/parse_error_diagnostics/selector_expressions.rs +++ /dev/null @@ -1,354 +0,0 @@ -use super::{Diagnostic, DiagnosticBuilder, FoundAt, found_at}; -use crate::lexer::{TokenSpan, TokenizedFile}; -use crate::parser::{ParseError, diagnostic_labels}; - -const PERIOD: &str = "period"; -const LEFT_BRACKET: &str = "left_bracket"; -const CALLEE: &str = "callee"; -const LEFT_PARENTHESIS: &str = "left_parenthesis"; -const PREVIOUS_ARGUMENT: &str = "previous_argument"; -const ARGUMENT: &str = "argument"; - -/// P0028 -pub(super) fn diagnostic_member_access_missing_member_name<'src>( - error: ParseError, - file: &TokenizedFile<'src>, -) -> Diagnostic { - let period_span = error.related_spans.get(PERIOD).copied(); - let blame_span = error.blame_span; - - let period_context_span = match period_span { - Some(period_span) if !file.same_line(period_span.start, blame_span.end) => { - Some(period_span) - } - _ => None, - }; - - let found = found_at(file, blame_span.start); - - let title = match found { - FoundAt::Token(token_text) => { - format!("expected member name after `.`, found `{}`", token_text) - } - FoundAt::EndOfFile => "expected member name after `.`, found end of file".to_string(), - FoundAt::Unknown => "expected member name after `.`".to_string(), - }; - - let primary_text = match found { - FoundAt::Token(token_text) => format!("unexpected `{}`", token_text), - FoundAt::EndOfFile => "reached end of file here".to_string(), - FoundAt::Unknown => "expected member name here".to_string(), - }; - - let mut builder = DiagnosticBuilder::error(title); - - if let Some(period_context_span) = period_context_span { - builder = builder.secondary_label( - period_context_span, - "after this `.`, a member name was expected", - ); - } - - builder - .primary_label(blame_span, primary_text) - .code("P0028") - .build() -} - -/// P0029 -pub(super) fn diagnostic_index_missing_expression<'src>( - error: ParseError, - file: &TokenizedFile<'src>, -) -> Diagnostic { - let left_bracket_span = error - .related_spans - .get(diagnostic_labels::EXPRESSION_EXPECTED_AFTER) - .copied(); - - let blame_span = error.blame_span; - - let left_bracket_context_span = match left_bracket_span { - Some(left_bracket_span) if !file.same_line(left_bracket_span.start, blame_span.end) => { - Some(left_bracket_span) - } - _ => None, - }; - - let primary_span = match left_bracket_context_span { - Some(left_bracket_context_span) => TokenSpan { - start: left_bracket_context_span.start, - end: blame_span.end, - }, - None => blame_span, - }; - - let found = found_at(file, blame_span.start); - - let title = match found { - FoundAt::Token(token_text) => { - format!("expected index expression after `[`, found `{}`", token_text) - } - FoundAt::EndOfFile => "expected index expression after `[`, found end of file".to_string(), - FoundAt::Unknown => "expected index expression after `[`".to_string(), - }; - - let primary_text = match found { - FoundAt::Token("]") => "expected expression before `]`".to_string(), - FoundAt::Token(token_text) => format!("unexpected `{}`", token_text), - FoundAt::EndOfFile => "reached end of file here".to_string(), - FoundAt::Unknown => "expected index expression here".to_string(), - }; - - let mut builder = DiagnosticBuilder::error(title); - - if let Some(left_bracket_context_span) = left_bracket_context_span { - builder = builder.secondary_label( - left_bracket_context_span, - "after this `[`, an index expression was expected", - ); - } - - builder - .primary_label(primary_span, primary_text) - .code("P0029") - .build() -} - -/// P0030 -pub(super) fn diagnostic_index_missing_closing_bracket<'src>( - error: ParseError, - file: &TokenizedFile<'src>, -) -> Diagnostic { - let left_bracket_span = error.related_spans.get(LEFT_BRACKET).copied(); - let blame_span = error.blame_span; - - let left_bracket_context_span = match left_bracket_span { - Some(left_bracket_span) if !file.same_line(left_bracket_span.start, blame_span.end) => { - Some(left_bracket_span) - } - _ => None, - }; - - let primary_span = match left_bracket_context_span { - Some(left_bracket_context_span) => TokenSpan { - start: left_bracket_context_span.start, - end: blame_span.end, - }, - None => blame_span, - }; - - let primary_text = match found_at(file, blame_span.start) { - FoundAt::Token(token_text) => format!("expected `]` before `{}`", token_text), - FoundAt::EndOfFile => "expected `]` before end of file".to_string(), - FoundAt::Unknown => "expected `]` here".to_string(), - }; - - let mut builder = DiagnosticBuilder::error("missing `]` to close index selector"); - - if let Some(left_bracket_context_span) = left_bracket_context_span { - builder = builder.secondary_label(left_bracket_context_span, "index selector starts here"); - } - - builder - .primary_label(primary_span, primary_text) - .code("P0030") - .build() -} - -/// P0031 -pub(super) fn diagnostic_function_call_argument_missing_comma<'src>( - error: ParseError, - file: &TokenizedFile<'src>, -) -> Diagnostic { - let callee_span = error.related_spans.get(CALLEE).copied(); - let left_parenthesis_span = error.related_spans.get(LEFT_PARENTHESIS).copied(); - let previous_argument_span = error.related_spans.get(PREVIOUS_ARGUMENT).copied(); - let blame_span = error.blame_span; - - let argument_list_context_span = match left_parenthesis_span { - Some(left_parenthesis_span) - if !file.same_line(left_parenthesis_span.start, blame_span.end) => - { - Some(left_parenthesis_span) - } - _ => None, - }; - - let callee_context_span = match (callee_span, left_parenthesis_span) { - (Some(callee_span), Some(left_parenthesis_span)) - if !file.same_line(callee_span.end, left_parenthesis_span.start) - && !file.same_line(callee_span.end, blame_span.end) => - { - Some(callee_span) - } - _ => None, - }; - - let primary_text = match found_at(file, blame_span.start) { - FoundAt::Token(token_text) => format!("expected `,` before `{}`", token_text), - FoundAt::EndOfFile => "expected `,` before end of file".to_string(), - FoundAt::Unknown => "expected `,` here".to_string(), - }; - - let mut builder = DiagnosticBuilder::error("missing `,` between function call arguments"); - - if let Some(callee_context_span) = callee_context_span { - builder = builder.secondary_label(callee_context_span, "function called here"); - } - - if let Some(argument_list_context_span) = argument_list_context_span { - builder = builder.secondary_label( - argument_list_context_span, - "function call argument list starts here", - ); - } - - if let Some(previous_argument_span) = previous_argument_span { - builder = builder.secondary_label(previous_argument_span, "previous argument ends here"); - } - - builder - .primary_label(blame_span, primary_text) - .code("P0031") - .build() -} - -/// P0032 -pub(super) fn diagnostic_function_call_missing_closing_parenthesis<'src>( - error: ParseError, - file: &TokenizedFile<'src>, -) -> Diagnostic { - let callee_span = error.related_spans.get(CALLEE).copied(); - let left_parenthesis_span = error.related_spans.get(LEFT_PARENTHESIS).copied(); - let blame_span = error.blame_span; - - let argument_list_context_span = match left_parenthesis_span { - Some(left_parenthesis_span) - if !file.same_line(left_parenthesis_span.start, blame_span.end) => - { - Some(left_parenthesis_span) - } - _ => None, - }; - - let callee_context_span = match (callee_span, left_parenthesis_span) { - (Some(callee_span), Some(left_parenthesis_span)) - if !file.same_line(callee_span.end, left_parenthesis_span.start) - && !file.same_line(callee_span.end, blame_span.end) => - { - Some(callee_span) - } - _ => None, - }; - - let primary_span = match argument_list_context_span { - Some(argument_list_context_span) => TokenSpan { - start: argument_list_context_span.start, - end: blame_span.end, - }, - None => blame_span, - }; - - let primary_text = match found_at(file, blame_span.start) { - FoundAt::Token(token_text) => format!("expected `)` before `{}`", token_text), - FoundAt::EndOfFile => "expected `)` before end of file".to_string(), - FoundAt::Unknown => "expected `)` here".to_string(), - }; - - let mut builder = DiagnosticBuilder::error("missing `)` to close function call argument list"); - - if let Some(callee_context_span) = callee_context_span { - builder = builder.secondary_label(callee_context_span, "function called here"); - } - - if let Some(argument_list_context_span) = argument_list_context_span { - builder = builder.secondary_label( - argument_list_context_span, - "function call argument list starts here", - ); - } - - builder - .primary_label(primary_span, primary_text) - .code("P0032") - .build() -} - -/// P0033 -pub(super) fn diagnostic_function_call_unexpected_token_in_argument_list<'src>( - error: ParseError, - file: &TokenizedFile<'src>, -) -> Diagnostic { - let callee_span = error.related_spans.get(CALLEE).copied(); - let left_parenthesis_span = error.related_spans.get(LEFT_PARENTHESIS).copied(); - let argument_span = error - .related_spans - .get(ARGUMENT) - .or_else(|| error.related_spans.get(PREVIOUS_ARGUMENT)) - .copied(); - let blame_span = error.blame_span; - - let argument_or_blame_span = match argument_span { - Some(argument_span) => argument_span, - None => blame_span, - }; - - let argument_list_context_span = match left_parenthesis_span { - Some(left_parenthesis_span) - if !file.same_line(left_parenthesis_span.start, argument_or_blame_span.end) => - { - Some(left_parenthesis_span) - } - _ => None, - }; - - let callee_context_span = match (callee_span, left_parenthesis_span) { - (Some(callee_span), Some(left_parenthesis_span)) - if !file.same_line(callee_span.end, left_parenthesis_span.start) - && !file.same_line(callee_span.end, argument_or_blame_span.end) => - { - Some(callee_span) - } - _ => None, - }; - - let found = found_at(file, blame_span.start); - - let title = match found { - FoundAt::Token(token_text) => { - format!("expected `,` or `)` after argument, found `{}`", token_text) - } - FoundAt::EndOfFile => { - "expected `,` or `)` after argument, found end of file".to_string() - } - FoundAt::Unknown => "expected `,` or `)` after argument".to_string(), - }; - - let primary_text = match found { - FoundAt::Token(token_text) => format!("unexpected `{}`", token_text), - FoundAt::EndOfFile => "reached end of file here".to_string(), - FoundAt::Unknown => "expected `,` or `)` here".to_string(), - }; - - let mut builder = DiagnosticBuilder::error(title); - - if let Some(callee_context_span) = callee_context_span { - builder = builder.secondary_label(callee_context_span, "function called here"); - } - - if let Some(argument_list_context_span) = argument_list_context_span { - builder = builder.secondary_label( - argument_list_context_span, - "function call argument list starts here", - ); - } - - if let Some(argument_span) = argument_span { - builder = builder.secondary_label(argument_span, "argument ends here"); - } - - builder - .primary_label(blame_span, primary_text) - .code("P0033") - .build() -} \ No newline at end of file diff --git a/rottlib/src/parser/errors.rs b/rottlib/src/parser/errors.rs index 9a355fd..9eec540 100644 --- a/rottlib/src/parser/errors.rs +++ b/rottlib/src/parser/errors.rs @@ -70,19 +70,10 @@ pub enum ParseErrorKind { BlockMissingClosingBrace, /// P0027 BlockExpectedItem, - /// P0028 - MemberAccessMissingMemberName, - /// P0029 - IndexMissingExpression, - /// P0030 - IndexMissingClosingBracket, - /// P0031 - FunctionCallArgumentMissingComma, - /// P0032 - FunctionCallMissingClosingParenthesis, - /// P0033 - FunctionCallUnexpectedTokenInArgumentList, // ================== Old errors to be thrown away! ================== + /// Expression inside `(...)` could not be parsed and no closing `)` + /// was found. + FunctionCallMissingClosingParenthesis, /// Found an unexpected token while parsing an expression. ExpressionUnexpectedToken, DeclEmptyVariableDeclarations, diff --git a/rottlib/src/parser/grammar/expression/block.rs b/rottlib/src/parser/grammar/expression/block.rs index ef54273..361b195 100644 --- a/rottlib/src/parser/grammar/expression/block.rs +++ b/rottlib/src/parser/grammar/expression/block.rs @@ -1,4 +1,4 @@ -//! Block-body parsing for Fermented UnrealScript. +//! Block-body parsing for Fermented `UnrealScript`. //! //! Provides shared routines for parsing `{ ... }`-delimited bodies used in //! function, loop, state, and similar constructs after the opening `{` @@ -115,7 +115,6 @@ impl<'src, 'arena> Parser<'src, 'arena> { ParseErrorKind::BlockMissingSemicolonAfterExpression, ) .widen_error_span_from(statement.span().start) - .sync_error_until(self, SyncLevel::StatementStart) .blame_token(unexpected_token_position) .related("expression_span", *statement.span()) .report(self); @@ -146,9 +145,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { if expression_recovery_made_no_progress { return self.recover_bad_block_item_start_as_error_statement(error); } - error - .sync_error_until(self, SyncLevel::StatementStart) - .fallback(self) + error.fallback(self) } }; let expression_span = *expression.span(); diff --git a/rottlib/src/parser/grammar/expression/pratt.rs b/rottlib/src/parser/grammar/expression/pratt.rs index 570eb5d..60a8d26 100644 --- a/rottlib/src/parser/grammar/expression/pratt.rs +++ b/rottlib/src/parser/grammar/expression/pratt.rs @@ -34,9 +34,7 @@ use crate::ast::{self, Expression, ExpressionRef}; use crate::lexer::TokenPosition; -use crate::parser::{ - self, ParseErrorKind, ParseExpressionResult, Parser, ResultRecoveryExt, diagnostic_labels, -}; +use crate::parser::{self, ParseExpressionResult, Parser, ResultRecoveryExt, diagnostic_labels}; pub use super::precedence::PrecedenceRank; @@ -59,7 +57,6 @@ fn forbids_postfix_operators(expression: &ExpressionRef<'_, '_>) -> bool { } impl<'src, 'arena> Parser<'src, 'arena> { - // TODO: success here guaranees progress /// Parses an expression. /// /// Always returns some expression node; any syntax errors are reported @@ -88,7 +85,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { /// the [`diagnostic_labels::EXPRESSION_EXPECTED_AFTER`] label. pub(super) fn parse_expression_with_start_error( &mut self, - bad_start_error_kind: ParseErrorKind, + bad_start_error_kind: crate::parser::ParseErrorKind, required_by_position: crate::lexer::TokenPosition, expression_context_position: crate::lexer::TokenPosition, ) -> ParseExpressionResult<'src, 'arena> { @@ -130,7 +127,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { min_precedence_rank: PrecedenceRank, ) -> parser::ParseExpressionResult<'src, 'arena> { let mut left_hand_side = self.parse_prefix_or_primary()?; - left_hand_side = self.parse_selectors_after(left_hand_side)?; + left_hand_side = self.parse_selectors_into(left_hand_side)?; // We disallow only postfix operators after expression forms that // represent control-flow or block constructs. Selectors are still // parsed normally. @@ -146,23 +143,16 @@ impl<'src, 'arena> Parser<'src, 'arena> { // because neither `--` or `++` (the only existing default postfix // operators) make any sense after such expressions anyway. if !forbids_postfix_operators(&left_hand_side) { - left_hand_side = self.parse_postfix_after(left_hand_side); + left_hand_side = self.parse_postfix_into(left_hand_side); } - self.parse_infix_after(left_hand_side, min_precedence_rank) + self.parse_infix_into(left_hand_side, min_precedence_rank) } /// Parses a prefix or primary expression (Pratt parser's "nud" or /// null denotation). fn parse_prefix_or_primary(&mut self) -> parser::ParseExpressionResult<'src, 'arena> { let (token, token_lexeme, token_position) = - self.require_token_lexeme_and_position(ParseErrorKind::ExpressionExpected)?; - // Avoid advancing over an obviously wrong token; - // this prevents error cases like `new(Outer, Name, 7 +) SomeClass`. - if token.is_definitely_not_expression_start() { - return Err( - self.make_error_at(ParseErrorKind::ExpressionExpected, token_position) - ); - } + self.require_token_lexeme_and_position(parser::ParseErrorKind::ExpressionExpected)?; self.advance(); if let Ok(operator) = ast::PrefixOperator::try_from(token) { // In UnrealScript, prefix and postfix operators bind tighter than @@ -184,7 +174,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { /// Parses all postfix operators it can, creating a tree with /// `left_hand_side` as a child. - fn parse_postfix_after( + fn parse_postfix_into( &mut self, mut left_hand_side: ExpressionRef<'src, 'arena>, ) -> ExpressionRef<'src, 'arena> { @@ -203,7 +193,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { /// [`super::precedence::infix_precedence_ranks`]. /// /// Stops when the next operator is looser than `min_precedence_rank`. - fn parse_infix_after( + fn parse_infix_into( &mut self, mut left_hand_side: ExpressionRef<'src, 'arena>, min_precedence_rank: PrecedenceRank, diff --git a/rottlib/src/parser/grammar/expression/primary/new.rs b/rottlib/src/parser/grammar/expression/primary/new.rs index 503e95f..0cc6b2d 100644 --- a/rottlib/src/parser/grammar/expression/primary/new.rs +++ b/rottlib/src/parser/grammar/expression/primary/new.rs @@ -1,6 +1,6 @@ //! Parser for `new` expressions in Fermented UnrealScript. -use super::super::selectors::{CallArgumentListParseState, ParsedArgumentSlot}; +use super::super::selectors::{CallArgumentListParseState, ParsedCallArgumentSlot}; use crate::ast::{Expression, ExpressionRef, OptionalExpression}; use crate::lexer::{Token, TokenPosition, TokenSpan}; use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel}; @@ -51,7 +51,7 @@ struct NewArgumentListParseState<'src, 'arena> { impl<'src, 'arena> NewArgumentListParseState<'src, 'arena> { /// Stores an argument in the current `new` argument slot. fn store_argument_in_current_slot(&mut self, argument: OptionalExpression<'src, 'arena>) { - match self.call_argument_list_parse_state.parsed_argument_slot_count { + match self.call_argument_list_parse_state.parsed_slot_count { 1 => self.outer_argument = argument, 2 => self.name_argument = argument, 3 => self.flags_argument = argument, @@ -65,10 +65,10 @@ impl<'src, 'arena> NewArgumentListParseState<'src, 'arena> { #[must_use] fn current_argument_span(&self) -> Option { debug_assert!( - (1..=3).contains(&self.call_argument_list_parse_state.parsed_argument_slot_count), + (1..=3).contains(&self.call_argument_list_parse_state.parsed_slot_count), "parsed_slot_count out of range in new-argument parser" ); - match self.call_argument_list_parse_state.parsed_argument_slot_count { + match self.call_argument_list_parse_state.parsed_slot_count { 1 => &self.outer_argument, 2 => &self.name_argument, 3 => &self.flags_argument, @@ -190,9 +190,9 @@ impl<'src, 'arena> Parser<'src, 'arena> { ) -> Option { // Only successful slot parses continue the loop, // so each iteration makes progress. - while state.call_argument_list_parse_state.parsed_argument_slot_count < 3 - && let ParsedArgumentSlot::Argument(argument) = - self.parse_next_call_argument_slot(&mut state.call_argument_list_parse_state) + while state.call_argument_list_parse_state.parsed_slot_count < 3 + && let ParsedCallArgumentSlot::Argument(argument) = + self.parse_call_argument_slot(&mut state.call_argument_list_parse_state) { // On `ParsedCallArgumentSlot::Argument(_)`, // `parse_call_argument_slot` increases `parsed_slot_count` by 1, @@ -202,7 +202,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { if state .call_argument_list_parse_state - .last_slot_missing_separator + .last_slot_missing_boundary { if let Some(class_specifier_parse_action) = self.recover_from_missing_new_argument_separator(state) @@ -223,7 +223,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { state: &mut NewArgumentListParseState<'src, 'arena>, ) -> Option { let has_parsed_all_allowed_arguments = - state.call_argument_list_parse_state.parsed_argument_slot_count >= 3; + state.call_argument_list_parse_state.parsed_slot_count > 2; let likely_missing_comma = !self.next_token_definitely_cannot_start_expression() && !has_parsed_all_allowed_arguments; if likely_missing_comma { @@ -259,10 +259,10 @@ impl<'src, 'arena> Parser<'src, 'arena> { // Preserve the first extra argument span for a more precise // diagnostic before we do any syncing. let first_extra_argument_span = - match self.parse_next_call_argument_slot(&mut state.call_argument_list_parse_state) { - ParsedArgumentSlot::Argument(Some(argument)) => Some(*argument.span()), - ParsedArgumentSlot::Argument(None) => None, - ParsedArgumentSlot::NoMoreArguments => None, + match self.parse_call_argument_slot(&mut state.call_argument_list_parse_state) { + ParsedCallArgumentSlot::Argument(Some(argument)) => Some(*argument.span()), + ParsedCallArgumentSlot::Argument(None) => None, + ParsedCallArgumentSlot::NoMoreArguments => None, }; let mut error = self .make_error_at_last_consumed(ParseErrorKind::NewTooManyArguments) @@ -282,42 +282,29 @@ impl<'src, 'arena> Parser<'src, 'arena> { } } - /// Recovers from a missing closing `)` in a `new(...)` argument list. - /// - /// Returns whether class-specifier parsing should continue after recovery. + /// Reports a missing closing `)` in a `new(...)` argument list and + /// determines whether the class specifier should be parsed or skipped. #[must_use] fn recover_from_missing_new_closing_parenthesis( &mut self, state: &NewArgumentListParseState<'src, 'arena>, ) -> NewClassSpecifierParseAction { - self.make_error_at_last_consumed(ParseErrorKind::NewMissingClosingParenthesis) + let mut error = self + .make_error_at_last_consumed(ParseErrorKind::NewMissingClosingParenthesis) .widen_error_span_from(state.left_parenthesis_position) .blame_token(self.peek_position_or_eof()) .related_token("new_keyword", state.new_keyword_position) - .related_token("left_parenthesis", state.left_parenthesis_position) - .report(self); - // Missing-delimiter recovery normally syncs to the matching `)`. - // `new(...) ClassName` is an exception: after a missing `)`, the next - // expression may already be the class specifier, not another argument. - let matching_right_parenthesis_ahead = self - .file - .matching_delimiter(state.left_parenthesis_position) - .is_some_and(|right_parenthesis_position| { - self.peek_position_or_eof() <= right_parenthesis_position - }); - if matching_right_parenthesis_ahead { - self.recover_at_matching_delimiter_or_sync(state.left_parenthesis_position); - // After syncing through the matched `)`, the argument-list error is - // contained, so class-specifier parsing can proceed normally. - return NewClassSpecifierParseAction::Parse; - } - if self.next_token_definitely_cannot_start_expression() { - // There is no plausible class specifier to parse, so skip it to - // avoid error cascade. + .related_token("left_parenthesis", state.left_parenthesis_position); + let class_specifier_parse_action = if self.next_token_definitely_cannot_start_expression() { + error = error.sync_error_at_matching_delimiter(self, state.left_parenthesis_position); + // Skipping the class specifier avoids cascading errors when + // the next token cannot start an expression anyway. NewClassSpecifierParseAction::Skip } else { NewClassSpecifierParseAction::Parse - } + }; + error.report_error(self); + class_specifier_parse_action } /// Parses the class specifier of a `new` expression after argument-list diff --git a/rottlib/src/parser/grammar/expression/selectors.rs b/rottlib/src/parser/grammar/expression/selectors.rs index 0e8febd..9d49441 100644 --- a/rottlib/src/parser/grammar/expression/selectors.rs +++ b/rottlib/src/parser/grammar/expression/selectors.rs @@ -1,126 +1,129 @@ -//! Parser support for expression selectors. +//! Parser for expression selectors in Fermented `UnrealScript`. //! -//! Selectors are suffix forms that require an already parsed left-hand side, +//! Selectors are suffix forms that extend an already parsed expression, //! such as member access, indexing, and calls. +//! +//! Unlike primaries, selectors cannot be parsed on their own from the +//! current token. They always require a left-hand side expression. -use crate::ast::{self, ExpressionRef}; -use crate::lexer::{self, Token, TokenPosition}; +use crate::arena::ArenaVec; +use crate::ast::{Expression, ExpressionRef, OptionalExpression}; +use crate::lexer::{Token, TokenPosition, TokenSpan}; use crate::parser::{ParseErrorKind, ParseExpressionResult, Parser, ResultRecoveryExt, SyncLevel}; -// Lack of `Copy` is deliberate to avoid accidental reuse of parser state. -#[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct CallArgumentListParseState { - /// Number of argument slots already yielded, including omitted slots. - pub(super) parsed_argument_slot_count: usize, +// TODO: think about importing/moving out these fucking structs a level up. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct CallArgumentListParseState { + /// Number of argument slots already returned as `Argument(...)`. + /// + /// This counts omitted slots too, for example in `f(,x)` or `f(x,,z)`. + pub parsed_slot_count: usize, - /// Whether the last yielded argument expression lacked a following - /// separator (',' or ')' or end-of-file). - pub(super) last_slot_missing_separator: bool, + /// Whether the most recently returned argument slot was not followed by + /// a valid argument boundary such as `,` or `)`. + /// + /// This flag is reset at the start of each call, so after + /// `NoMoreArguments` it is always `false`. + pub last_slot_missing_boundary: bool, } impl CallArgumentListParseState { #[must_use] - pub(super) fn new() -> Self { + pub(crate) fn new() -> Self { Self { - parsed_argument_slot_count: 0, - last_slot_missing_separator: false, + parsed_slot_count: 0, + last_slot_missing_boundary: false, } } #[must_use] - fn has_parsed_any_argument_slots(&self) -> bool { - self.parsed_argument_slot_count > 0 + pub(crate) fn is_first_slot(&self) -> bool { + self.parsed_slot_count == 0 } } /// Represents the result of parsing one call argument slot. +/// +/// This distinguishes between the end of the argument list and a parsed +/// argument slot, including an omitted one. #[must_use] #[derive(Debug, PartialEq)] -pub(super) enum ParsedArgumentSlot<'src, 'arena> { - /// No further slots should be parsed. +pub enum ParsedCallArgumentSlot<'src, 'arena> { + /// Indicates that the argument list has ended. NoMoreArguments, - /// A parsed slot. `None` represents an omitted argument. - Argument(ast::OptionalExpression<'src, 'arena>), + /// The parsed argument for this slot. + /// + /// `None` represents an omitted argument between commas. + Argument(OptionalExpression<'src, 'arena>), } impl<'src, 'arena> Parser<'src, 'arena> { /// Parses zero or more postfix selectors after `left_hand_side`. /// /// Returns the resulting expression after all contiguous selectors. - pub(super) fn parse_selectors_after( + pub(crate) fn parse_selectors_into( &mut self, - mut left_hand_side: ExpressionRef<'src, 'arena>, + left_hand_side: ExpressionRef<'src, 'arena>, ) -> ParseExpressionResult<'src, 'arena> { - while let Some((next_token, next_token_position)) = self.peek_token_and_position() { + let mut left_hand_side = left_hand_side; + // `next_position` is used only to widen diagnostic spans. + while let Some((next_token, next_position)) = self.peek_token_and_position() { left_hand_side = match next_token { - Token::Period => { - self.advance(); // '.' - self.parse_member_access_selector_after(left_hand_side, next_token_position)? - } + Token::Period => self.parse_selector_member_access_into(left_hand_side)?, Token::LeftBracket => { - self.advance(); // '[' - self.parse_index_selector_after(left_hand_side, next_token_position)? + self.parse_selector_index_into(left_hand_side, next_position)? } Token::LeftParenthesis => { - self.advance(); // '(' - self.parse_call_selector_after(left_hand_side, next_token_position) + self.parse_selector_call_into(left_hand_side, next_position) } _ => break, }; - self.ensure_forward_progress(next_token_position); } Ok(left_hand_side) } /// Parses a member access selector after `left_hand_side`. /// - /// Expects the leading `.` to have already been consumed. - fn parse_member_access_selector_after( + /// Expects the leading `.` to be the next token and returns the resulting + /// member access expression. + fn parse_selector_member_access_into( &mut self, left_hand_side: ExpressionRef<'src, 'arena>, - period_position: TokenPosition, ) -> ParseExpressionResult<'src, 'arena> { + self.advance(); // `.` let member_access_start = left_hand_side.span().start; - let member_name_position = self.peek_position_or_eof(); - let member_name = self - .parse_identifier(ParseErrorKind::MemberAccessMissingMemberName) - .blame_token(member_name_position) - .related_token("period", period_position)?; - let member_access_end = member_name.0; + let member_identifier = self.parse_identifier(ParseErrorKind::ExpressionUnexpectedToken)?; + let member_access_end = member_identifier.0; Ok(self.arena.alloc_node( - ast::Expression::Member { + Expression::Member { target: left_hand_side, - name: member_name, + name: member_identifier, }, - lexer::TokenSpan::range(member_access_start, member_access_end), + TokenSpan::range(member_access_start, member_access_end), )) } /// Parses an index selector after `left_hand_side`. /// - /// Expects the leading `[` to have already been consumed. - fn parse_index_selector_after( + /// Expects the leading `[` to be the next token and returns the resulting + /// indexing expression. + fn parse_selector_index_into( &mut self, left_hand_side: ExpressionRef<'src, 'arena>, left_bracket_position: TokenPosition, ) -> ParseExpressionResult<'src, 'arena> { - let index_expression = self - .parse_expression_with_start_error( - ParseErrorKind::IndexMissingExpression, - left_hand_side.span().end, - left_bracket_position, - ) - .sync_error_at_matching_delimiter(self, left_bracket_position)?; + self.advance(); // '[' + let index_expression = self.parse_expression(); let right_bracket_position = self .expect( Token::RightBracket, - ParseErrorKind::IndexMissingClosingBracket, + ParseErrorKind::ExpressionUnexpectedToken, ) .widen_error_span_from(left_bracket_position) - .sync_error_at_matching_delimiter(self, left_bracket_position) - .related_token("left_bracket", left_bracket_position)?; + .sync_error_at(self, SyncLevel::CloseBracket)?; + let expression_start = left_hand_side.span().start; Ok(self.arena.alloc_node_between( - ast::Expression::Index { + Expression::Index { target: left_hand_side, index: index_expression, }, @@ -131,31 +134,27 @@ impl<'src, 'arena> Parser<'src, 'arena> { /// Parses a call selector after `left_hand_side`. /// - /// Expects the leading `(` to have already been consumed. - /// Reports malformed argument lists internally and still returns - /// a call expression. - fn parse_call_selector_after( + /// Expects the leading `(` to be the next token and returns the resulting + /// call expression. + fn parse_selector_call_into( &mut self, left_hand_side: ExpressionRef<'src, 'arena>, left_parenthesis_position: TokenPosition, ) -> ExpressionRef<'src, 'arena> { - let callee_end_position = left_hand_side.span().end; - let argument_list = - self.parse_call_argument_list(callee_end_position, left_parenthesis_position); + self.advance(); // '(' + let argument_list = self.parse_call_argument_list(left_parenthesis_position); let right_parenthesis_position = self .expect( Token::RightParenthesis, ParseErrorKind::FunctionCallMissingClosingParenthesis, ) .widen_error_span_from(left_parenthesis_position) - .sync_error_at_matching_delimiter(self, left_parenthesis_position) - .related_token("callee", callee_end_position) - .related_token("left_parenthesis", left_parenthesis_position) + .sync_error_at(self, SyncLevel::CloseParenthesis) .unwrap_or_fallback(self); let expression_start = left_hand_side.span().start; self.arena.alloc_node_between( - ast::Expression::Call { + Expression::Call { callee: left_hand_side, arguments: argument_list, }, @@ -164,157 +163,96 @@ impl<'src, 'arena> Parser<'src, 'arena> { ) } - /// Parses a call argument list after an already-consumed `(`. + // TODO: add note that `parsed_slot_count` is guaranteed to be incremented + // by 1 at most (and when). + // TODO: say that errors must be handled by caller. + /// Parses one call argument slot after an already consumed `(`. /// - /// Returns all parsed argument slots, preserving omitted arguments - /// as `None`. Does not consume the closing `)`. - fn parse_call_argument_list( - &mut self, - callee_end_position: TokenPosition, - left_parenthesis_position: TokenPosition, - ) -> ast::ArgumentList<'src, 'arena> { - let mut argument_list = crate::arena::ArenaVec::new_in(self.arena); - let mut argument_list_state = CallArgumentListParseState::new(); - - let mut progress_checkpoint = None; - while let ParsedArgumentSlot::Argument(argument) = - self.parse_next_call_argument_slot(&mut argument_list_state) - { - if let Some(progress_checkpoint) = progress_checkpoint { - self.ensure_forward_progress(progress_checkpoint); - } - let parsed_argument_span = argument.as_ref().map(|argument| *argument.span()); - argument_list.push(argument); - if argument_list_state.last_slot_missing_separator { - if !self.recover_after_missing_function_call_argument_separator( - callee_end_position, - left_parenthesis_position, - parsed_argument_span, - ) { - break; - } - } - progress_checkpoint = self.peek_position(); - } - argument_list - } - - /// Parses the next logical call-argument slot. + /// In `UnrealScript`, every comma introduces a follow-up argument slot, so a + /// trailing comma immediately before `)` denotes an omitted final argument. /// - /// In UnrealScript, commas introduce follow-up argument slots, so `f(x,)` - /// means `f(x, )`, not a call with a tolerated trailing separator. + /// Returns [`ParsedCallArgumentSlot::NoMoreArguments`] when the argument list + /// ends, and `Argument(None)` for an omitted argument slot. /// - /// Returns [`ParsedArgumentSlot::NoMoreArguments`] when the argument list - /// has ended or no safe recovery can continue it. - /// Returns [`ParsedArgumentSlot::Argument`] for a parsed slot, including - /// omitted slots. - /// - /// Repeated calls with the same `state` are guaranteed to eventually return - /// [`ParsedArgumentSlot::NoMoreArguments`], even for malformed input. - /// - /// Records per-slot status in `state`. - pub(super) fn parse_next_call_argument_slot( + /// Per-call status is recorded into `state`. + pub(crate) fn parse_call_argument_slot( &mut self, state: &mut CallArgumentListParseState, - ) -> ParsedArgumentSlot<'src, 'arena> { - state.last_slot_missing_separator = false; + ) -> ParsedCallArgumentSlot<'src, 'arena> { + state.last_slot_missing_boundary = false; - // A comma belongs to the next slot because a final comma represents an - // omitted final argument, not a tolerated trailing separator. + // This function consumes arguments one at a time and the way we chose + // to handle this is by consuming a comma *before* each new argument, + // not *after*. + // Normal (non-empty) case of special argument will simply skip this + // `match`. But first *empty* argument must be handled as + // a special case. match self.peek_token() { None | Some(Token::RightParenthesis) => { - return ParsedArgumentSlot::NoMoreArguments; + return ParsedCallArgumentSlot::NoMoreArguments; } Some(Token::Comma) => { - // In `f(,x)`, the leading comma both creates the omitted first - // slot and separates it from `x`, so the first slot must not - // consume it. - if state.has_parsed_any_argument_slots() { + // We handle special case of first empty argument by *not* + // consuming first comma (it will be consumed together with + // the second argument). + // + // We do change parsing state by incrementing + // `state.parsed_slot_count`, which ensures that + // `is_first_slot()` will return `false` from now on. + if !state.is_first_slot() { self.advance(); } - if self.is_at_call_argument_boundary() { - state.parsed_argument_slot_count += 1; - return ParsedArgumentSlot::Argument(None); + // This `if`'s body is guaranteed to run if we've skipped + // `advance()` above. + if self.at_call_argument_boundary() { + state.parsed_slot_count += 1; + return ParsedCallArgumentSlot::Argument(None); } } _ => (), } - let position_before_argument = self.peek_position_or_eof(); - let mut argument = self.parse_expression(); + let argument = self.parse_expression(); + state.parsed_slot_count += 1; + state.last_slot_missing_boundary = !self.at_call_argument_boundary(); - let expression_recovery_made_no_progress = - self.peek_position_or_eof() == position_before_argument; - if expression_recovery_made_no_progress { - self.recover_until(SyncLevel::ListSeparator); - let list_level_recovery_made_no_progress = - self.peek_position_or_eof() == position_before_argument; - if list_level_recovery_made_no_progress { - return ParsedArgumentSlot::NoMoreArguments; - } else { - argument - .span_mut() - .extend_to(self.last_consumed_position_or_start()); - } - } - state.parsed_argument_slot_count += 1; - state.last_slot_missing_separator = !self.is_at_call_argument_boundary(); - ParsedArgumentSlot::Argument(Some(argument)) + ParsedCallArgumentSlot::Argument(Some(argument)) } - /// Reports and recovers from a missing call-argument separator. + /// Parses a call argument list after an already-consumed `(`. /// - /// Returns whether argument-list parsing can continue at - /// the recovered position. - #[must_use] - fn recover_after_missing_function_call_argument_separator( + /// Returns all parsed argument slots, preserving omitted arguments + /// as `None`. + fn parse_call_argument_list( &mut self, - callee_end_position: TokenPosition, left_parenthesis_position: TokenPosition, - previous_argument_span: Option, - ) -> bool { - if self.next_token_definitely_cannot_start_expression() { - let unexpected_token_position = self.peek_position_or_eof(); - let mut error = self - .make_error_at( - ParseErrorKind::FunctionCallUnexpectedTokenInArgumentList, - unexpected_token_position, - ) - .widen_error_span_from(left_parenthesis_position) - .sync_error_until(self, SyncLevel::ListSeparator) - .blame_token(unexpected_token_position) - .related_token("callee", callee_end_position) - .related_token("left_parenthesis", left_parenthesis_position); - if let Some(previous_argument_span) = previous_argument_span { - error = error.related("argument", previous_argument_span); - } - error.report(self); - self.is_at_call_argument_boundary() - } else { - let next_argument_position = self.peek_position_or_eof(); - let mut error = self - .make_error_at( - ParseErrorKind::FunctionCallArgumentMissingComma, - next_argument_position, - ) - .blame_token(next_argument_position) - .related_token("callee", callee_end_position) - .related_token("left_parenthesis", left_parenthesis_position); - debug_assert!(previous_argument_span.is_some()); - if let Some(previous_argument_span) = previous_argument_span { - error = error.related("previous_argument", previous_argument_span); - } - error.report(self); - true + ) -> ArenaVec<'arena, Option>> { + let mut argument_list = ArenaVec::new_in(self.arena); + + let mut call_state = CallArgumentListParseState::new(); + //let mut old_position = self.peek_position_or_eof(); + // This caused infinite loop? (on eof?) what? + while let ParsedCallArgumentSlot::Argument(argument) = + self.parse_call_argument_slot(&mut call_state) + { + argument_list.push(argument); + // TODO: ensure progress here shouldn't be necessary actually + //self.ensure_forward_progress(old_position); + //old_position = self.peek_position_or_eof(); } + + argument_list } - /// Returns whether the current token is a call-argument boundary. - #[must_use] - fn is_at_call_argument_boundary(&mut self) -> bool { + /// Returns whether the current lookahead token ends the current call + /// argument slot. + /// + /// This is true for `,`, which starts the next slot, and for `)`, which + /// ends the argument list. + fn at_call_argument_boundary(&mut self) -> bool { matches!( self.peek_token(), - None | Some(Token::Comma | Token::RightParenthesis) + Some(Token::Comma | Token::RightParenthesis) ) } } diff --git a/rottlib/src/parser/recovery.rs b/rottlib/src/parser/recovery.rs index ee5b87f..cbf1e2a 100644 --- a/rottlib/src/parser/recovery.rs +++ b/rottlib/src/parser/recovery.rs @@ -243,10 +243,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { return; } - while let Some(position) = self.peek_position() { - if position >= target { - break; - } + while self.peek_position_or_eof() < target { self.advance(); } } @@ -282,10 +279,7 @@ impl<'src, 'arena> Parser<'src, 'arena> { return; } - while let Some(position) = self.peek_position() { - if position >= target { - break; - } + while self.peek_position_or_eof() < target { self.advance(); } diff --git a/rottlib/tests/parser_diagnostics/mod.rs b/rottlib/tests/parser_diagnostics/mod.rs index 55d1686..426a3ef 100644 --- a/rottlib/tests/parser_diagnostics/mod.rs +++ b/rottlib/tests/parser_diagnostics/mod.rs @@ -8,7 +8,6 @@ use rottlib::parser::Parser; mod block_items; mod control_flow_expressions; mod primary_expressions; -mod selector_expressions; #[derive(Debug)] pub(super) struct ExpectedLabel { diff --git a/rottlib/tests/parser_diagnostics/primary_expressions.rs b/rottlib/tests/parser_diagnostics/primary_expressions.rs index 1dec025..2d161cb 100644 --- a/rottlib/tests/parser_diagnostics/primary_expressions.rs +++ b/rottlib/tests/parser_diagnostics/primary_expressions.rs @@ -35,10 +35,6 @@ pub(super) const P0002_FIXTURES: &[Fixture] = &[ label: "files/P0002_04.uc", source: "a * * *", }, - Fixture { - label: "files/P0002_05.uc", - source: "new(Outer, Name, 7 +) SomeClass" - } ]; pub(super) const P0003_FIXTURES: &[Fixture] = &[ @@ -326,7 +322,6 @@ fn check_p0002_fixtures() { assert_eq!(runs.get("files/P0002_02.uc").unwrap().len(), 1); assert_eq!(runs.get("files/P0002_03.uc").unwrap().len(), 1); assert_eq!(runs.get("files/P0002_04.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0002_05.uc").unwrap().len(), 1); assert_diagnostic( &runs.get_any("files/P0002_01.uc"), @@ -409,25 +404,6 @@ fn check_p0002_fixtures() { notes: &[], }, ); - - assert_diagnostic( - &runs.get_any("files/P0002_05.uc"), - &ExpectedDiagnostic { - headline: "expected expression after `+`, found `)`", - severity: Severity::Error, - code: Some("P0002"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(11), - end: TokenPosition(11), - }, - message: "unexpected `)`", - }), - secondary_labels: &[], - help: None, - notes: &[], - }, - ); } #[test] @@ -689,7 +665,7 @@ fn check_p0005_fixtures() { code: Some("P0005"), primary_label: Some(ExpectedLabel { span: TokenSpan { - start: TokenPosition(14), + start: TokenPosition(9), end: TokenPosition(14), }, message: "unexpected `>`", @@ -723,7 +699,7 @@ fn check_p0005_fixtures() { code: Some("P0005"), primary_label: Some(ExpectedLabel { span: TokenSpan { - start: TokenPosition(10), + start: TokenPosition(5), end: TokenPosition(10), }, message: "unexpected `>`", diff --git a/rottlib/tests/parser_diagnostics/selector_expressions.rs b/rottlib/tests/parser_diagnostics/selector_expressions.rs deleted file mode 100644 index 767e24a..0000000 --- a/rottlib/tests/parser_diagnostics/selector_expressions.rs +++ /dev/null @@ -1,778 +0,0 @@ -use super::*; - -pub(super) const P0028_FIXTURES: &[Fixture] = &[ - Fixture { - label: "files/P0028_01.uc", - source: "{\n local Actor A;\n A.\n}\n", - }, - Fixture { - label: "files/P0028_02.uc", - source: "{\n local Actor A;\n A.;\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0028_03.uc", - source: "{\n local Actor A;\n A.\n ;\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0028_04.uc", - source: "{\n local Actor A;\n Log(A.\n );\n Log(\"after\");\n}\n", - }, -]; - -pub(super) const P0029_FIXTURES: &[Fixture] = &[ - Fixture { - label: "files/P0029_01.uc", - source: "{\n local array Values;\n Values[];\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0029_02.uc", - source: "{\n local array Values;\n Values[\n ];\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0029_03.uc", - source: "{\n local array Values;\n Values[, 1];\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0029_04.uc", - source: "{\n local array Values;\n Log(Values[\n ]);\n Log(\"after\");\n}\n", - }, -]; - -pub(super) const P0030_FIXTURES: &[Fixture] = &[ - Fixture { - label: "files/P0030_01.uc", - source: "{\n local array Values;\n Values[0;\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0030_02.uc", - source: "{\n local array Values;\n Values[\n 0\n ;\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0030_03.uc", - source: "{\n local array Values;\n Log(Values[0));\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0030_04.uc", - source: "{\n local array Values;\n Values[GetIndex()\n Values[1] = 7;\n}\n", - }, -]; - -#[test] -fn check_p0028_fixtures() { - let runs = run_fixtures(P0028_FIXTURES); - - assert_eq!(runs.get("files/P0028_01.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0028_02.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0028_03.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0028_04.uc").unwrap().len(), 1); - - assert_diagnostic( - &runs.get_any("files/P0028_01.uc"), - &ExpectedDiagnostic { - headline: "expected member name after `.`, found `}`", - severity: Severity::Error, - code: Some("P0028"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(14), - end: TokenPosition(14), - }, - message: "unexpected `}`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(12), - end: TokenPosition(12), - }, - message: "after this `.`, a member name was expected", - }], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0028_02.uc"), - &ExpectedDiagnostic { - headline: "expected member name after `.`, found `;`", - severity: Severity::Error, - code: Some("P0028"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(13), - end: TokenPosition(13), - }, - message: "unexpected `;`", - }), - secondary_labels: &[], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0028_03.uc"), - &ExpectedDiagnostic { - headline: "expected member name after `.`, found `;`", - severity: Severity::Error, - code: Some("P0028"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(15), - end: TokenPosition(15), - }, - message: "unexpected `;`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(12), - end: TokenPosition(12), - }, - message: "after this `.`, a member name was expected", - }], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0028_04.uc"), - &ExpectedDiagnostic { - headline: "expected member name after `.`, found `)`", - severity: Severity::Error, - code: Some("P0028"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(17), - end: TokenPosition(17), - }, - message: "unexpected `)`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(14), - end: TokenPosition(14), - }, - message: "after this `.`, a member name was expected", - }], - help: None, - notes: &[], - }, - ); -} - -#[test] -fn check_p0029_fixtures() { - let runs = run_fixtures(P0029_FIXTURES); - - assert_eq!(runs.get("files/P0029_01.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0029_02.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0029_03.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0029_04.uc").unwrap().len(), 1); - - assert_diagnostic( - &runs.get_any("files/P0029_01.uc"), - &ExpectedDiagnostic { - headline: "expected index expression after `[`, found `]`", - severity: Severity::Error, - code: Some("P0029"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(16), - end: TokenPosition(16), - }, - message: "expected expression before `]`", - }), - secondary_labels: &[], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0029_02.uc"), - &ExpectedDiagnostic { - headline: "expected index expression after `[`, found `]`", - severity: Severity::Error, - code: Some("P0029"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(15), - end: TokenPosition(18), - }, - message: "expected expression before `]`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(15), - end: TokenPosition(15), - }, - message: "after this `[`, an index expression was expected", - }], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0029_03.uc"), - &ExpectedDiagnostic { - headline: "expected index expression after `[`, found `,`", - severity: Severity::Error, - code: Some("P0029"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(16), - end: TokenPosition(16), - }, - message: "unexpected `,`", - }), - secondary_labels: &[], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0029_04.uc"), - &ExpectedDiagnostic { - headline: "expected index expression after `[`, found `]`", - severity: Severity::Error, - code: Some("P0029"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(17), - end: TokenPosition(20), - }, - message: "expected expression before `]`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(17), - end: TokenPosition(17), - }, - message: "after this `[`, an index expression was expected", - }], - help: None, - notes: &[], - }, - ); -} - -#[test] -fn check_p0030_fixtures() { - let runs = run_fixtures(P0030_FIXTURES); - - assert_eq!(runs.get("files/P0030_01.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0030_02.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0030_04.uc").unwrap().len(), 1); - - assert_diagnostic( - &runs.get_any("files/P0030_01.uc"), - &ExpectedDiagnostic { - headline: "missing `]` to close index selector", - severity: Severity::Error, - code: Some("P0030"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(17), - end: TokenPosition(17), - }, - message: "expected `]` before `;`", - }), - secondary_labels: &[], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0030_02.uc"), - &ExpectedDiagnostic { - headline: "missing `]` to close index selector", - severity: Severity::Error, - code: Some("P0030"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(15), - end: TokenPosition(21), - }, - message: "expected `]` before `;`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(15), - end: TokenPosition(15), - }, - message: "index selector starts here", - }], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_by_code("files/P0030_03.uc", "P0030"), - &ExpectedDiagnostic { - headline: "missing `]` to close index selector", - severity: Severity::Error, - code: Some("P0030"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(19), - end: TokenPosition(19), - }, - message: "expected `]` before `)`", - }), - secondary_labels: &[], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0030_04.uc"), - &ExpectedDiagnostic { - headline: "missing `]` to close index selector", - severity: Severity::Error, - code: Some("P0030"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(15), - end: TokenPosition(21), - }, - message: "expected `]` before `Values`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(15), - end: TokenPosition(15), - }, - message: "index selector starts here", - }], - help: None, - notes: &[], - }, - ); -} - -pub(super) const P0031_FIXTURES: &[Fixture] = &[ - Fixture { - label: "files/P0031_01.uc", - source: "{\n Func(A B);\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0031_02.uc", - source: "{\n Func\n (A 123);\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0031_03.uc", - source: "{\n Func(\n A\n new SomeClass\n );\n Log(\"after\");\n}\n", - }, -]; - -pub(super) const P0032_FIXTURES: &[Fixture] = &[ - Fixture { - label: "files/P0032_01.uc", - source: "Func(", - }, - Fixture { - label: "files/P0032_02.uc", - source: "Func\n(\n A,", - }, - Fixture { - label: "files/P0032_03.uc", - source: "Func(A,\n B,", - }, - Fixture { - label: "files/P0032_04.uc", - source: "{\n Func\n (\n A,\n B,\n", - }, -]; - -pub(super) const P0033_FIXTURES: &[Fixture] = &[ - Fixture { - label: "files/P0033_01.uc", - source: "{\n Func(A #, B);\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0033_02.uc", - source: "{\n Func\n (A\n :,\n B);\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0033_03.uc", - source: "{\n Func(\n A ?\n , B\n );\n Log(\"after\");\n}\n", - }, - Fixture { - label: "files/P0033_04.uc", - source: "{\n Func\n (\n A\n #,\n B\n );\n Log(\"after\");\n}\n", - }, -]; - -#[test] -fn check_p0031_fixtures() { - let runs = run_fixtures(P0031_FIXTURES); - - assert_eq!(runs.get("files/P0031_01.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0031_02.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0031_03.uc").unwrap().len(), 1); - - assert_diagnostic( - &runs.get_any("files/P0031_01.uc"), - &ExpectedDiagnostic { - headline: "missing `,` between function call arguments", - severity: Severity::Error, - code: Some("P0031"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(7), - end: TokenPosition(7), - }, - message: "expected `,` before `B`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(5), - end: TokenPosition(5), - }, - message: "previous argument ends here", - }], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0031_02.uc"), - &ExpectedDiagnostic { - headline: "missing `,` between function call arguments", - severity: Severity::Error, - code: Some("P0031"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(9), - end: TokenPosition(9), - }, - message: "expected `,` before `123`", - }), - secondary_labels: &[ - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(3), - end: TokenPosition(3), - }, - message: "function called here", - }, - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(7), - end: TokenPosition(7), - }, - message: "previous argument ends here", - }, - ], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0031_03.uc"), - &ExpectedDiagnostic { - headline: "missing `,` between function call arguments", - severity: Severity::Error, - code: Some("P0031"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(10), - end: TokenPosition(10), - }, - message: "expected `,` before `new`", - }), - secondary_labels: &[ - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(4), - end: TokenPosition(4), - }, - message: "function call argument list starts here", - }, - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(7), - end: TokenPosition(7), - }, - message: "previous argument ends here", - }, - ], - help: None, - notes: &[], - }, - ); -} - -#[test] -fn check_p0032_fixtures() { - let runs = run_fixtures(P0032_FIXTURES); - - assert_eq!(runs.get("files/P0032_01.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0032_02.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0032_03.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0032_04.uc").unwrap().len(), 2); - - assert_diagnostic( - &runs.get_any("files/P0032_01.uc"), - &ExpectedDiagnostic { - headline: "missing `)` to close function call argument list", - severity: Severity::Error, - code: Some("P0032"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(2), - end: TokenPosition(2), - }, - message: "expected `)` before end of file", - }), - secondary_labels: &[], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0032_02.uc"), - &ExpectedDiagnostic { - headline: "missing `)` to close function call argument list", - severity: Severity::Error, - code: Some("P0032"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(2), - end: TokenPosition(7), - }, - message: "expected `)` before end of file", - }), - secondary_labels: &[ - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(0), - end: TokenPosition(0), - }, - message: "function called here", - }, - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(2), - end: TokenPosition(2), - }, - message: "function call argument list starts here", - }, - ], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0032_03.uc"), - &ExpectedDiagnostic { - headline: "missing `)` to close function call argument list", - severity: Severity::Error, - code: Some("P0032"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(1), - end: TokenPosition(8), - }, - message: "expected `)` before end of file", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(1), - end: TokenPosition(1), - }, - message: "function call argument list starts here", - }], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_by_code("files/P0032_04.uc", "P0032"), - &ExpectedDiagnostic { - headline: "missing `)` to close function call argument list", - severity: Severity::Error, - code: Some("P0032"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(6), - end: TokenPosition(16), - }, - message: "expected `)` before end of file", - }), - secondary_labels: &[ - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(3), - end: TokenPosition(3), - }, - message: "function called here", - }, - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(6), - end: TokenPosition(6), - }, - message: "function call argument list starts here", - }, - ], - help: None, - notes: &[], - }, - ); -} - -#[test] -fn check_p0033_fixtures() { - let runs = run_fixtures(P0033_FIXTURES); - - assert_eq!(runs.get("files/P0033_01.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0033_02.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0033_03.uc").unwrap().len(), 1); - assert_eq!(runs.get("files/P0033_04.uc").unwrap().len(), 1); - - assert_diagnostic( - &runs.get_any("files/P0033_01.uc"), - &ExpectedDiagnostic { - headline: "expected `,` or `)` after argument, found `#`", - severity: Severity::Error, - code: Some("P0033"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(7), - end: TokenPosition(7), - }, - message: "unexpected `#`", - }), - secondary_labels: &[ExpectedLabel { - span: TokenSpan { - start: TokenPosition(5), - end: TokenPosition(5), - }, - message: "argument ends here", - }], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0033_02.uc"), - &ExpectedDiagnostic { - headline: "expected `,` or `)` after argument, found `:`", - severity: Severity::Error, - code: Some("P0033"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(10), - end: TokenPosition(10), - }, - message: "unexpected `:`", - }), - secondary_labels: &[ - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(3), - end: TokenPosition(3), - }, - message: "function called here", - }, - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(7), - end: TokenPosition(7), - }, - message: "argument ends here", - }, - ], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0033_03.uc"), - &ExpectedDiagnostic { - headline: "expected `,` or `)` after argument, found `?`", - severity: Severity::Error, - code: Some("P0033"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(9), - end: TokenPosition(9), - }, - message: "unexpected `?`", - }), - secondary_labels: &[ - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(4), - end: TokenPosition(4), - }, - message: "function call argument list starts here", - }, - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(7), - end: TokenPosition(7), - }, - message: "argument ends here", - }, - ], - help: None, - notes: &[], - }, - ); - - assert_diagnostic( - &runs.get_any("files/P0033_04.uc"), - &ExpectedDiagnostic { - headline: "expected `,` or `)` after argument, found `#`", - severity: Severity::Error, - code: Some("P0033"), - primary_label: Some(ExpectedLabel { - span: TokenSpan { - start: TokenPosition(12), - end: TokenPosition(12), - }, - message: "unexpected `#`", - }), - secondary_labels: &[ - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(3), - end: TokenPosition(3), - }, - message: "function called here", - }, - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(6), - end: TokenPosition(6), - }, - message: "function call argument list starts here", - }, - ExpectedLabel { - span: TokenSpan { - start: TokenPosition(9), - end: TokenPosition(9), - }, - message: "argument ends here", - }, - ], - help: None, - notes: &[], - }, - ); -} \ No newline at end of file