Compare commits

..

No commits in common. "e29ffb2a9c41c6594c455936a49eb38f029631ed" and "b1f071448329299388e445d45d44a877e8b54d6c" have entirely different histories.

14 changed files with 207 additions and 1498 deletions

View File

@ -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",
),
];

View File

@ -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<ExpressionRef<'src, 'arena>>>,
},
/// 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>>;

View File

@ -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")

View File

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

View File

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

View File

@ -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,

View File

@ -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();

View File

@ -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,

View File

@ -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<TokenSpan> {
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<NewClassSpecifierParseAction> {
// 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<NewClassSpecifierParseAction> {
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

View File

@ -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, <omitted>)`, 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<lexer::TokenSpan>,
) -> 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<ExpressionRef<'src, 'arena>>> {
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();
}
/// Returns whether the current token is a call-argument boundary.
#[must_use]
fn is_at_call_argument_boundary(&mut self) -> bool {
argument_list
}
/// 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)
)
}
}

View File

@ -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();
}

View File

@ -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 {

View File

@ -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 `>`",

View File

@ -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<int> Values;\n Values[];\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0029_02.uc",
source: "{\n local array<int> Values;\n Values[\n ];\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0029_03.uc",
source: "{\n local array<int> Values;\n Values[, 1];\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0029_04.uc",
source: "{\n local array<int> 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<int> Values;\n Values[0;\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0030_02.uc",
source: "{\n local array<int> Values;\n Values[\n 0\n ;\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0030_03.uc",
source: "{\n local array<int> Values;\n Log(Values[0));\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0030_04.uc",
source: "{\n local array<int> 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: &[],
},
);
}