Compare commits
No commits in common. "e29ffb2a9c41c6594c455936a49eb38f029631ed" and "b1f071448329299388e445d45d44a877e8b54d6c" have entirely different histories.
e29ffb2a9c
...
b1f0714483
@ -16,43 +16,40 @@ use rottlib::parser::Parser;
|
|||||||
/// Keep these small: the goal is to inspect lexer diagnostics and delimiter
|
/// Keep these small: the goal is to inspect lexer diagnostics and delimiter
|
||||||
/// recovery behavior, not full parser behavior.
|
/// recovery behavior, not full parser behavior.
|
||||||
const TEST_CASES: &[(&str, &str)] = &[
|
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",
|
"files/P0027_01.uc",
|
||||||
"{\n Func(A B);\n Log(\"after\");\n}\n",
|
"{\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",
|
"files/P0027_02.uc",
|
||||||
"{\n Func\n (A 123);\n Log(\"after\");\n}\n",
|
"{ 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",
|
"files/P0027_03.uc",
|
||||||
"{\n Func(\n A\n new SomeClass\n );\n Log(\"after\");\n}\n",
|
"{\n local bool bDone;\n bDone = false;\n until (bDone)\n TickWork();\n}\n",
|
||||||
),
|
),
|
||||||
// P0032 - FunctionCallMissingClosingParenthesis
|
|
||||||
("files/P0032_01.uc", "Func("),
|
// P0027: preprocessor/exec directive inside a statement block
|
||||||
("files/P0032_02.uc", "Func\n(\n A,"),
|
//
|
||||||
("files/P0032_03.uc", "Func(A,\n B,"),
|
// `#exec` is declaration/top-level-like syntax, not a valid statement or
|
||||||
|
// expression inside a braced statement block.
|
||||||
(
|
(
|
||||||
"files/P0032_04.uc",
|
"files/P0027_04.uc",
|
||||||
"{\n Func\n (\n A,\n B,\n",
|
"{\n local int Count;\n Count = 0;\n #exec TEXTURE IMPORT NAME=Bad FILE=Bad.bmp\n Count++;\n}\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",
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
//!
|
//!
|
||||||
//! This module defines ordinary expressions together with expression-shaped
|
//! This module defines ordinary expressions together with expression-shaped
|
||||||
//! control-flow and block forms parsed by the language.
|
//! control-flow and block forms parsed by the language.
|
||||||
use super::{
|
use super::{IdentifierToken, InfixOperator, PostfixOperator, PrefixOperator,
|
||||||
IdentifierToken, InfixOperator, PostfixOperator, PrefixOperator, QualifiedIdentifierRef,
|
QualifiedIdentifierRef, StatementRef,
|
||||||
StatementRef,
|
|
||||||
};
|
};
|
||||||
use crate::arena::ArenaVec;
|
|
||||||
use crate::lexer::TokenSpan;
|
use crate::lexer::TokenSpan;
|
||||||
|
use crate::arena::ArenaVec;
|
||||||
|
|
||||||
use super::super::lexer::TokenPosition;
|
use super::super::lexer::TokenPosition;
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ pub enum Expression<'src, 'arena> {
|
|||||||
/// arguments in syntaxes that allow empty slots.
|
/// arguments in syntaxes that allow empty slots.
|
||||||
Call {
|
Call {
|
||||||
callee: ExpressionRef<'src, 'arena>,
|
callee: ExpressionRef<'src, 'arena>,
|
||||||
arguments: ArgumentList<'src, 'arena>,
|
arguments: ArenaVec<'arena, Option<ExpressionRef<'src, 'arena>>>,
|
||||||
},
|
},
|
||||||
/// Prefix unary operator application: `op rhs`.
|
/// Prefix unary operator application: `op rhs`.
|
||||||
PrefixUnary(PrefixOperator, ExpressionRef<'src, 'arena>),
|
PrefixUnary(PrefixOperator, ExpressionRef<'src, 'arena>),
|
||||||
@ -180,9 +179,6 @@ pub enum Expression<'src, 'arena> {
|
|||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arguments in any comma-separated list.
|
|
||||||
pub type ArgumentList<'src, 'arena> = ArenaVec<'arena, OptionalExpression<'src, 'arena>>;
|
|
||||||
|
|
||||||
/// Statements contained in a `{ ... }` block.
|
/// Statements contained in a `{ ... }` block.
|
||||||
pub type StatementList<'src, 'arena> = ArenaVec<'arena, StatementRef<'src, 'arena>>;
|
pub type StatementList<'src, 'arena> = ArenaVec<'arena, StatementRef<'src, 'arena>>;
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@ use crate::parser::{ParseError, ParseErrorKind};
|
|||||||
mod block_items;
|
mod block_items;
|
||||||
mod control_flow_expressions;
|
mod control_flow_expressions;
|
||||||
mod primary_expressions;
|
mod primary_expressions;
|
||||||
mod selector_expressions;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum FoundAt<'src> {
|
enum FoundAt<'src> {
|
||||||
@ -77,7 +76,6 @@ pub(crate) fn diagnostic_from_parse_error<'src>(
|
|||||||
) -> Diagnostic {
|
) -> Diagnostic {
|
||||||
use control_flow_expressions::*;
|
use control_flow_expressions::*;
|
||||||
use primary_expressions::*;
|
use primary_expressions::*;
|
||||||
use selector_expressions::*;
|
|
||||||
match error.kind {
|
match error.kind {
|
||||||
// primary_expressions.rs
|
// primary_expressions.rs
|
||||||
ParseErrorKind::ParenthesizedExpressionInvalidStart => {
|
ParseErrorKind::ParenthesizedExpressionInvalidStart => {
|
||||||
@ -148,23 +146,6 @@ pub(crate) fn diagnostic_from_parse_error<'src>(
|
|||||||
diagnostic_block_missing_closing_brace(error, file)
|
diagnostic_block_missing_closing_brace(error, file)
|
||||||
}
|
}
|
||||||
ParseErrorKind::BlockExpectedItem => diagnostic_block_expected_item(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))
|
_ => DiagnosticBuilder::error(format!("error {:?} while parsing", error.kind))
|
||||||
.primary_label(error.covered_span, "happened here")
|
.primary_label(error.covered_span, "happened here")
|
||||||
|
|||||||
@ -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 qualifier_dot_span = error.related_spans.get("qualifier_dot").copied();
|
||||||
let class_span = error.related_spans.get("class_keyword").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, error.blame_span.end) {
|
||||||
|
|
||||||
let (header_text, primary_text) = match found_at(file, blame_pos) {
|
|
||||||
FoundAt::Token(token_text) => (
|
FoundAt::Token(token_text) => (
|
||||||
format!(
|
format!(
|
||||||
"expected another type segment after `.`, found `{}`",
|
"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);
|
let mut builder = DiagnosticBuilder::error(header_text);
|
||||||
|
|
||||||
if let Some(dot_span) = qualifier_dot_span {
|
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(
|
builder = builder.secondary_label(
|
||||||
dot_span,
|
dot_span,
|
||||||
"after this `.`, another type segment was expected",
|
"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 {
|
let primary_span = collapse_span_to_end_on_same_line(file, error.blame_span);
|
||||||
start: blame_pos,
|
|
||||||
end: blame_pos,
|
|
||||||
};
|
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.primary_label(primary_span, primary_text)
|
.primary_label(primary_span, primary_text)
|
||||||
|
|||||||
@ -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()
|
|
||||||
}
|
|
||||||
@ -70,19 +70,10 @@ pub enum ParseErrorKind {
|
|||||||
BlockMissingClosingBrace,
|
BlockMissingClosingBrace,
|
||||||
/// P0027
|
/// P0027
|
||||||
BlockExpectedItem,
|
BlockExpectedItem,
|
||||||
/// P0028
|
|
||||||
MemberAccessMissingMemberName,
|
|
||||||
/// P0029
|
|
||||||
IndexMissingExpression,
|
|
||||||
/// P0030
|
|
||||||
IndexMissingClosingBracket,
|
|
||||||
/// P0031
|
|
||||||
FunctionCallArgumentMissingComma,
|
|
||||||
/// P0032
|
|
||||||
FunctionCallMissingClosingParenthesis,
|
|
||||||
/// P0033
|
|
||||||
FunctionCallUnexpectedTokenInArgumentList,
|
|
||||||
// ================== Old errors to be thrown away! ==================
|
// ================== 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.
|
/// Found an unexpected token while parsing an expression.
|
||||||
ExpressionUnexpectedToken,
|
ExpressionUnexpectedToken,
|
||||||
DeclEmptyVariableDeclarations,
|
DeclEmptyVariableDeclarations,
|
||||||
|
|||||||
@ -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
|
//! Provides shared routines for parsing `{ ... }`-delimited bodies used in
|
||||||
//! function, loop, state, and similar constructs after the opening `{`
|
//! function, loop, state, and similar constructs after the opening `{`
|
||||||
@ -115,7 +115,6 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
ParseErrorKind::BlockMissingSemicolonAfterExpression,
|
ParseErrorKind::BlockMissingSemicolonAfterExpression,
|
||||||
)
|
)
|
||||||
.widen_error_span_from(statement.span().start)
|
.widen_error_span_from(statement.span().start)
|
||||||
.sync_error_until(self, SyncLevel::StatementStart)
|
|
||||||
.blame_token(unexpected_token_position)
|
.blame_token(unexpected_token_position)
|
||||||
.related("expression_span", *statement.span())
|
.related("expression_span", *statement.span())
|
||||||
.report(self);
|
.report(self);
|
||||||
@ -146,9 +145,7 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
if expression_recovery_made_no_progress {
|
if expression_recovery_made_no_progress {
|
||||||
return self.recover_bad_block_item_start_as_error_statement(error);
|
return self.recover_bad_block_item_start_as_error_statement(error);
|
||||||
}
|
}
|
||||||
error
|
error.fallback(self)
|
||||||
.sync_error_until(self, SyncLevel::StatementStart)
|
|
||||||
.fallback(self)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let expression_span = *expression.span();
|
let expression_span = *expression.span();
|
||||||
|
|||||||
@ -34,9 +34,7 @@
|
|||||||
|
|
||||||
use crate::ast::{self, Expression, ExpressionRef};
|
use crate::ast::{self, Expression, ExpressionRef};
|
||||||
use crate::lexer::TokenPosition;
|
use crate::lexer::TokenPosition;
|
||||||
use crate::parser::{
|
use crate::parser::{self, ParseExpressionResult, Parser, ResultRecoveryExt, diagnostic_labels};
|
||||||
self, ParseErrorKind, ParseExpressionResult, Parser, ResultRecoveryExt, diagnostic_labels,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use super::precedence::PrecedenceRank;
|
pub use super::precedence::PrecedenceRank;
|
||||||
|
|
||||||
@ -59,7 +57,6 @@ fn forbids_postfix_operators(expression: &ExpressionRef<'_, '_>) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'src, 'arena> Parser<'src, 'arena> {
|
impl<'src, 'arena> Parser<'src, 'arena> {
|
||||||
// TODO: success here guaranees progress
|
|
||||||
/// Parses an expression.
|
/// Parses an expression.
|
||||||
///
|
///
|
||||||
/// Always returns some expression node; any syntax errors are reported
|
/// 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.
|
/// the [`diagnostic_labels::EXPRESSION_EXPECTED_AFTER`] label.
|
||||||
pub(super) fn parse_expression_with_start_error(
|
pub(super) fn parse_expression_with_start_error(
|
||||||
&mut self,
|
&mut self,
|
||||||
bad_start_error_kind: ParseErrorKind,
|
bad_start_error_kind: crate::parser::ParseErrorKind,
|
||||||
required_by_position: crate::lexer::TokenPosition,
|
required_by_position: crate::lexer::TokenPosition,
|
||||||
expression_context_position: crate::lexer::TokenPosition,
|
expression_context_position: crate::lexer::TokenPosition,
|
||||||
) -> ParseExpressionResult<'src, 'arena> {
|
) -> ParseExpressionResult<'src, 'arena> {
|
||||||
@ -130,7 +127,7 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
min_precedence_rank: PrecedenceRank,
|
min_precedence_rank: PrecedenceRank,
|
||||||
) -> parser::ParseExpressionResult<'src, 'arena> {
|
) -> parser::ParseExpressionResult<'src, 'arena> {
|
||||||
let mut left_hand_side = self.parse_prefix_or_primary()?;
|
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
|
// We disallow only postfix operators after expression forms that
|
||||||
// represent control-flow or block constructs. Selectors are still
|
// represent control-flow or block constructs. Selectors are still
|
||||||
// parsed normally.
|
// parsed normally.
|
||||||
@ -146,23 +143,16 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
// because neither `--` or `++` (the only existing default postfix
|
// because neither `--` or `++` (the only existing default postfix
|
||||||
// operators) make any sense after such expressions anyway.
|
// operators) make any sense after such expressions anyway.
|
||||||
if !forbids_postfix_operators(&left_hand_side) {
|
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
|
/// Parses a prefix or primary expression (Pratt parser's "nud" or
|
||||||
/// null denotation).
|
/// null denotation).
|
||||||
fn parse_prefix_or_primary(&mut self) -> parser::ParseExpressionResult<'src, 'arena> {
|
fn parse_prefix_or_primary(&mut self) -> parser::ParseExpressionResult<'src, 'arena> {
|
||||||
let (token, token_lexeme, token_position) =
|
let (token, token_lexeme, token_position) =
|
||||||
self.require_token_lexeme_and_position(ParseErrorKind::ExpressionExpected)?;
|
self.require_token_lexeme_and_position(parser::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.advance();
|
self.advance();
|
||||||
if let Ok(operator) = ast::PrefixOperator::try_from(token) {
|
if let Ok(operator) = ast::PrefixOperator::try_from(token) {
|
||||||
// In UnrealScript, prefix and postfix operators bind tighter than
|
// 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
|
/// Parses all postfix operators it can, creating a tree with
|
||||||
/// `left_hand_side` as a child.
|
/// `left_hand_side` as a child.
|
||||||
fn parse_postfix_after(
|
fn parse_postfix_into(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut left_hand_side: ExpressionRef<'src, 'arena>,
|
mut left_hand_side: ExpressionRef<'src, 'arena>,
|
||||||
) -> ExpressionRef<'src, 'arena> {
|
) -> ExpressionRef<'src, 'arena> {
|
||||||
@ -203,7 +193,7 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
/// [`super::precedence::infix_precedence_ranks`].
|
/// [`super::precedence::infix_precedence_ranks`].
|
||||||
///
|
///
|
||||||
/// Stops when the next operator is looser than `min_precedence_rank`.
|
/// Stops when the next operator is looser than `min_precedence_rank`.
|
||||||
fn parse_infix_after(
|
fn parse_infix_into(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut left_hand_side: ExpressionRef<'src, 'arena>,
|
mut left_hand_side: ExpressionRef<'src, 'arena>,
|
||||||
min_precedence_rank: PrecedenceRank,
|
min_precedence_rank: PrecedenceRank,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! Parser for `new` expressions in Fermented UnrealScript.
|
//! 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::ast::{Expression, ExpressionRef, OptionalExpression};
|
||||||
use crate::lexer::{Token, TokenPosition, TokenSpan};
|
use crate::lexer::{Token, TokenPosition, TokenSpan};
|
||||||
use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel};
|
use crate::parser::{ParseErrorKind, Parser, ResultRecoveryExt, SyncLevel};
|
||||||
@ -51,7 +51,7 @@ struct NewArgumentListParseState<'src, 'arena> {
|
|||||||
impl<'src, 'arena> NewArgumentListParseState<'src, 'arena> {
|
impl<'src, 'arena> NewArgumentListParseState<'src, 'arena> {
|
||||||
/// Stores an argument in the current `new` argument slot.
|
/// Stores an argument in the current `new` argument slot.
|
||||||
fn store_argument_in_current_slot(&mut self, argument: OptionalExpression<'src, 'arena>) {
|
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,
|
1 => self.outer_argument = argument,
|
||||||
2 => self.name_argument = argument,
|
2 => self.name_argument = argument,
|
||||||
3 => self.flags_argument = argument,
|
3 => self.flags_argument = argument,
|
||||||
@ -65,10 +65,10 @@ impl<'src, 'arena> NewArgumentListParseState<'src, 'arena> {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
fn current_argument_span(&self) -> Option<TokenSpan> {
|
fn current_argument_span(&self) -> Option<TokenSpan> {
|
||||||
debug_assert!(
|
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"
|
"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,
|
1 => &self.outer_argument,
|
||||||
2 => &self.name_argument,
|
2 => &self.name_argument,
|
||||||
3 => &self.flags_argument,
|
3 => &self.flags_argument,
|
||||||
@ -190,9 +190,9 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
) -> Option<NewClassSpecifierParseAction> {
|
) -> Option<NewClassSpecifierParseAction> {
|
||||||
// Only successful slot parses continue the loop,
|
// Only successful slot parses continue the loop,
|
||||||
// so each iteration makes progress.
|
// so each iteration makes progress.
|
||||||
while state.call_argument_list_parse_state.parsed_argument_slot_count < 3
|
while state.call_argument_list_parse_state.parsed_slot_count < 3
|
||||||
&& let ParsedArgumentSlot::Argument(argument) =
|
&& let ParsedCallArgumentSlot::Argument(argument) =
|
||||||
self.parse_next_call_argument_slot(&mut state.call_argument_list_parse_state)
|
self.parse_call_argument_slot(&mut state.call_argument_list_parse_state)
|
||||||
{
|
{
|
||||||
// On `ParsedCallArgumentSlot::Argument(_)`,
|
// On `ParsedCallArgumentSlot::Argument(_)`,
|
||||||
// `parse_call_argument_slot` increases `parsed_slot_count` by 1,
|
// `parse_call_argument_slot` increases `parsed_slot_count` by 1,
|
||||||
@ -202,7 +202,7 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
|
|
||||||
if state
|
if state
|
||||||
.call_argument_list_parse_state
|
.call_argument_list_parse_state
|
||||||
.last_slot_missing_separator
|
.last_slot_missing_boundary
|
||||||
{
|
{
|
||||||
if let Some(class_specifier_parse_action) =
|
if let Some(class_specifier_parse_action) =
|
||||||
self.recover_from_missing_new_argument_separator(state)
|
self.recover_from_missing_new_argument_separator(state)
|
||||||
@ -223,7 +223,7 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
state: &mut NewArgumentListParseState<'src, 'arena>,
|
state: &mut NewArgumentListParseState<'src, 'arena>,
|
||||||
) -> Option<NewClassSpecifierParseAction> {
|
) -> Option<NewClassSpecifierParseAction> {
|
||||||
let has_parsed_all_allowed_arguments =
|
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()
|
let likely_missing_comma = !self.next_token_definitely_cannot_start_expression()
|
||||||
&& !has_parsed_all_allowed_arguments;
|
&& !has_parsed_all_allowed_arguments;
|
||||||
if likely_missing_comma {
|
if likely_missing_comma {
|
||||||
@ -259,10 +259,10 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
// Preserve the first extra argument span for a more precise
|
// Preserve the first extra argument span for a more precise
|
||||||
// diagnostic before we do any syncing.
|
// diagnostic before we do any syncing.
|
||||||
let first_extra_argument_span =
|
let first_extra_argument_span =
|
||||||
match self.parse_next_call_argument_slot(&mut state.call_argument_list_parse_state) {
|
match self.parse_call_argument_slot(&mut state.call_argument_list_parse_state) {
|
||||||
ParsedArgumentSlot::Argument(Some(argument)) => Some(*argument.span()),
|
ParsedCallArgumentSlot::Argument(Some(argument)) => Some(*argument.span()),
|
||||||
ParsedArgumentSlot::Argument(None) => None,
|
ParsedCallArgumentSlot::Argument(None) => None,
|
||||||
ParsedArgumentSlot::NoMoreArguments => None,
|
ParsedCallArgumentSlot::NoMoreArguments => None,
|
||||||
};
|
};
|
||||||
let mut error = self
|
let mut error = self
|
||||||
.make_error_at_last_consumed(ParseErrorKind::NewTooManyArguments)
|
.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.
|
/// Reports a missing closing `)` in a `new(...)` argument list and
|
||||||
///
|
/// determines whether the class specifier should be parsed or skipped.
|
||||||
/// Returns whether class-specifier parsing should continue after recovery.
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn recover_from_missing_new_closing_parenthesis(
|
fn recover_from_missing_new_closing_parenthesis(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &NewArgumentListParseState<'src, 'arena>,
|
state: &NewArgumentListParseState<'src, 'arena>,
|
||||||
) -> NewClassSpecifierParseAction {
|
) -> 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)
|
.widen_error_span_from(state.left_parenthesis_position)
|
||||||
.blame_token(self.peek_position_or_eof())
|
.blame_token(self.peek_position_or_eof())
|
||||||
.related_token("new_keyword", state.new_keyword_position)
|
.related_token("new_keyword", state.new_keyword_position)
|
||||||
.related_token("left_parenthesis", state.left_parenthesis_position)
|
.related_token("left_parenthesis", state.left_parenthesis_position);
|
||||||
.report(self);
|
let class_specifier_parse_action = if self.next_token_definitely_cannot_start_expression() {
|
||||||
// Missing-delimiter recovery normally syncs to the matching `)`.
|
error = error.sync_error_at_matching_delimiter(self, state.left_parenthesis_position);
|
||||||
// `new(...) ClassName` is an exception: after a missing `)`, the next
|
// Skipping the class specifier avoids cascading errors when
|
||||||
// expression may already be the class specifier, not another argument.
|
// the next token cannot start an expression anyway.
|
||||||
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.
|
|
||||||
NewClassSpecifierParseAction::Skip
|
NewClassSpecifierParseAction::Skip
|
||||||
} else {
|
} else {
|
||||||
NewClassSpecifierParseAction::Parse
|
NewClassSpecifierParseAction::Parse
|
||||||
}
|
};
|
||||||
|
error.report_error(self);
|
||||||
|
class_specifier_parse_action
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the class specifier of a `new` expression after argument-list
|
/// Parses the class specifier of a `new` expression after argument-list
|
||||||
|
|||||||
@ -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.
|
//! 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::arena::ArenaVec;
|
||||||
use crate::lexer::{self, Token, TokenPosition};
|
use crate::ast::{Expression, ExpressionRef, OptionalExpression};
|
||||||
|
use crate::lexer::{Token, TokenPosition, TokenSpan};
|
||||||
use crate::parser::{ParseErrorKind, ParseExpressionResult, Parser, ResultRecoveryExt, SyncLevel};
|
use crate::parser::{ParseErrorKind, ParseExpressionResult, Parser, ResultRecoveryExt, SyncLevel};
|
||||||
|
|
||||||
// Lack of `Copy` is deliberate to avoid accidental reuse of parser state.
|
// TODO: think about importing/moving out these fucking structs a level up.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(super) struct CallArgumentListParseState {
|
pub(crate) struct CallArgumentListParseState {
|
||||||
/// Number of argument slots already yielded, including omitted slots.
|
/// Number of argument slots already returned as `Argument(...)`.
|
||||||
pub(super) parsed_argument_slot_count: usize,
|
///
|
||||||
|
/// 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
|
/// Whether the most recently returned argument slot was not followed by
|
||||||
/// separator (',' or ')' or end-of-file).
|
/// a valid argument boundary such as `,` or `)`.
|
||||||
pub(super) last_slot_missing_separator: bool,
|
///
|
||||||
|
/// 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 {
|
impl CallArgumentListParseState {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(super) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
parsed_argument_slot_count: 0,
|
parsed_slot_count: 0,
|
||||||
last_slot_missing_separator: false,
|
last_slot_missing_boundary: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn has_parsed_any_argument_slots(&self) -> bool {
|
pub(crate) fn is_first_slot(&self) -> bool {
|
||||||
self.parsed_argument_slot_count > 0
|
self.parsed_slot_count == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the result of parsing one call argument slot.
|
/// 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]
|
#[must_use]
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(super) enum ParsedArgumentSlot<'src, 'arena> {
|
pub enum ParsedCallArgumentSlot<'src, 'arena> {
|
||||||
/// No further slots should be parsed.
|
/// Indicates that the argument list has ended.
|
||||||
NoMoreArguments,
|
NoMoreArguments,
|
||||||
/// A parsed slot. `None` represents an omitted argument.
|
/// The parsed argument for this slot.
|
||||||
Argument(ast::OptionalExpression<'src, 'arena>),
|
///
|
||||||
|
/// `None` represents an omitted argument between commas.
|
||||||
|
Argument(OptionalExpression<'src, 'arena>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src, 'arena> Parser<'src, 'arena> {
|
impl<'src, 'arena> Parser<'src, 'arena> {
|
||||||
/// Parses zero or more postfix selectors after `left_hand_side`.
|
/// Parses zero or more postfix selectors after `left_hand_side`.
|
||||||
///
|
///
|
||||||
/// Returns the resulting expression after all contiguous selectors.
|
/// Returns the resulting expression after all contiguous selectors.
|
||||||
pub(super) fn parse_selectors_after(
|
pub(crate) fn parse_selectors_into(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut left_hand_side: ExpressionRef<'src, 'arena>,
|
left_hand_side: ExpressionRef<'src, 'arena>,
|
||||||
) -> ParseExpressionResult<'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 {
|
left_hand_side = match next_token {
|
||||||
Token::Period => {
|
Token::Period => self.parse_selector_member_access_into(left_hand_side)?,
|
||||||
self.advance(); // '.'
|
|
||||||
self.parse_member_access_selector_after(left_hand_side, next_token_position)?
|
|
||||||
}
|
|
||||||
Token::LeftBracket => {
|
Token::LeftBracket => {
|
||||||
self.advance(); // '['
|
self.parse_selector_index_into(left_hand_side, next_position)?
|
||||||
self.parse_index_selector_after(left_hand_side, next_token_position)?
|
|
||||||
}
|
}
|
||||||
Token::LeftParenthesis => {
|
Token::LeftParenthesis => {
|
||||||
self.advance(); // '('
|
self.parse_selector_call_into(left_hand_side, next_position)
|
||||||
self.parse_call_selector_after(left_hand_side, next_token_position)
|
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
};
|
};
|
||||||
self.ensure_forward_progress(next_token_position);
|
|
||||||
}
|
}
|
||||||
Ok(left_hand_side)
|
Ok(left_hand_side)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a member access selector after `left_hand_side`.
|
/// Parses a member access selector after `left_hand_side`.
|
||||||
///
|
///
|
||||||
/// Expects the leading `.` to have already been consumed.
|
/// Expects the leading `.` to be the next token and returns the resulting
|
||||||
fn parse_member_access_selector_after(
|
/// member access expression.
|
||||||
|
fn parse_selector_member_access_into(
|
||||||
&mut self,
|
&mut self,
|
||||||
left_hand_side: ExpressionRef<'src, 'arena>,
|
left_hand_side: ExpressionRef<'src, 'arena>,
|
||||||
period_position: TokenPosition,
|
|
||||||
) -> ParseExpressionResult<'src, 'arena> {
|
) -> ParseExpressionResult<'src, 'arena> {
|
||||||
|
self.advance(); // `.`
|
||||||
let member_access_start = left_hand_side.span().start;
|
let member_access_start = left_hand_side.span().start;
|
||||||
let member_name_position = self.peek_position_or_eof();
|
let member_identifier = self.parse_identifier(ParseErrorKind::ExpressionUnexpectedToken)?;
|
||||||
let member_name = self
|
let member_access_end = member_identifier.0;
|
||||||
.parse_identifier(ParseErrorKind::MemberAccessMissingMemberName)
|
|
||||||
.blame_token(member_name_position)
|
|
||||||
.related_token("period", period_position)?;
|
|
||||||
let member_access_end = member_name.0;
|
|
||||||
Ok(self.arena.alloc_node(
|
Ok(self.arena.alloc_node(
|
||||||
ast::Expression::Member {
|
Expression::Member {
|
||||||
target: left_hand_side,
|
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`.
|
/// Parses an index selector after `left_hand_side`.
|
||||||
///
|
///
|
||||||
/// Expects the leading `[` to have already been consumed.
|
/// Expects the leading `[` to be the next token and returns the resulting
|
||||||
fn parse_index_selector_after(
|
/// indexing expression.
|
||||||
|
fn parse_selector_index_into(
|
||||||
&mut self,
|
&mut self,
|
||||||
left_hand_side: ExpressionRef<'src, 'arena>,
|
left_hand_side: ExpressionRef<'src, 'arena>,
|
||||||
left_bracket_position: TokenPosition,
|
left_bracket_position: TokenPosition,
|
||||||
) -> ParseExpressionResult<'src, 'arena> {
|
) -> ParseExpressionResult<'src, 'arena> {
|
||||||
let index_expression = self
|
self.advance(); // '['
|
||||||
.parse_expression_with_start_error(
|
let index_expression = self.parse_expression();
|
||||||
ParseErrorKind::IndexMissingExpression,
|
|
||||||
left_hand_side.span().end,
|
|
||||||
left_bracket_position,
|
|
||||||
)
|
|
||||||
.sync_error_at_matching_delimiter(self, left_bracket_position)?;
|
|
||||||
let right_bracket_position = self
|
let right_bracket_position = self
|
||||||
.expect(
|
.expect(
|
||||||
Token::RightBracket,
|
Token::RightBracket,
|
||||||
ParseErrorKind::IndexMissingClosingBracket,
|
ParseErrorKind::ExpressionUnexpectedToken,
|
||||||
)
|
)
|
||||||
.widen_error_span_from(left_bracket_position)
|
.widen_error_span_from(left_bracket_position)
|
||||||
.sync_error_at_matching_delimiter(self, left_bracket_position)
|
.sync_error_at(self, SyncLevel::CloseBracket)?;
|
||||||
.related_token("left_bracket", left_bracket_position)?;
|
|
||||||
let expression_start = left_hand_side.span().start;
|
let expression_start = left_hand_side.span().start;
|
||||||
Ok(self.arena.alloc_node_between(
|
Ok(self.arena.alloc_node_between(
|
||||||
ast::Expression::Index {
|
Expression::Index {
|
||||||
target: left_hand_side,
|
target: left_hand_side,
|
||||||
index: index_expression,
|
index: index_expression,
|
||||||
},
|
},
|
||||||
@ -131,31 +134,27 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
|
|
||||||
/// Parses a call selector after `left_hand_side`.
|
/// Parses a call selector after `left_hand_side`.
|
||||||
///
|
///
|
||||||
/// Expects the leading `(` to have already been consumed.
|
/// Expects the leading `(` to be the next token and returns the resulting
|
||||||
/// Reports malformed argument lists internally and still returns
|
/// call expression.
|
||||||
/// a call expression.
|
fn parse_selector_call_into(
|
||||||
fn parse_call_selector_after(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
left_hand_side: ExpressionRef<'src, 'arena>,
|
left_hand_side: ExpressionRef<'src, 'arena>,
|
||||||
left_parenthesis_position: TokenPosition,
|
left_parenthesis_position: TokenPosition,
|
||||||
) -> ExpressionRef<'src, 'arena> {
|
) -> ExpressionRef<'src, 'arena> {
|
||||||
let callee_end_position = left_hand_side.span().end;
|
self.advance(); // '('
|
||||||
let argument_list =
|
let argument_list = self.parse_call_argument_list(left_parenthesis_position);
|
||||||
self.parse_call_argument_list(callee_end_position, left_parenthesis_position);
|
|
||||||
let right_parenthesis_position = self
|
let right_parenthesis_position = self
|
||||||
.expect(
|
.expect(
|
||||||
Token::RightParenthesis,
|
Token::RightParenthesis,
|
||||||
ParseErrorKind::FunctionCallMissingClosingParenthesis,
|
ParseErrorKind::FunctionCallMissingClosingParenthesis,
|
||||||
)
|
)
|
||||||
.widen_error_span_from(left_parenthesis_position)
|
.widen_error_span_from(left_parenthesis_position)
|
||||||
.sync_error_at_matching_delimiter(self, left_parenthesis_position)
|
.sync_error_at(self, SyncLevel::CloseParenthesis)
|
||||||
.related_token("callee", callee_end_position)
|
|
||||||
.related_token("left_parenthesis", left_parenthesis_position)
|
|
||||||
.unwrap_or_fallback(self);
|
.unwrap_or_fallback(self);
|
||||||
|
|
||||||
let expression_start = left_hand_side.span().start;
|
let expression_start = left_hand_side.span().start;
|
||||||
self.arena.alloc_node_between(
|
self.arena.alloc_node_between(
|
||||||
ast::Expression::Call {
|
Expression::Call {
|
||||||
callee: left_hand_side,
|
callee: left_hand_side,
|
||||||
arguments: argument_list,
|
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
|
/// In `UnrealScript`, every comma introduces a follow-up argument slot, so a
|
||||||
/// as `None`. Does not consume the closing `)`.
|
/// trailing comma immediately before `)` denotes an omitted final argument.
|
||||||
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, commas introduce follow-up argument slots, so `f(x,)`
|
/// Returns [`ParsedCallArgumentSlot::NoMoreArguments`] when the argument list
|
||||||
/// means `f(x, <omitted>)`, not a call with a tolerated trailing separator.
|
/// ends, and `Argument(None)` for an omitted argument slot.
|
||||||
///
|
///
|
||||||
/// Returns [`ParsedArgumentSlot::NoMoreArguments`] when the argument list
|
/// Per-call status is recorded into `state`.
|
||||||
/// has ended or no safe recovery can continue it.
|
pub(crate) fn parse_call_argument_slot(
|
||||||
/// 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(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut CallArgumentListParseState,
|
state: &mut CallArgumentListParseState,
|
||||||
) -> ParsedArgumentSlot<'src, 'arena> {
|
) -> ParsedCallArgumentSlot<'src, 'arena> {
|
||||||
state.last_slot_missing_separator = false;
|
state.last_slot_missing_boundary = false;
|
||||||
|
|
||||||
// A comma belongs to the next slot because a final comma represents an
|
// This function consumes arguments one at a time and the way we chose
|
||||||
// omitted final argument, not a tolerated trailing separator.
|
// 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() {
|
match self.peek_token() {
|
||||||
None | Some(Token::RightParenthesis) => {
|
None | Some(Token::RightParenthesis) => {
|
||||||
return ParsedArgumentSlot::NoMoreArguments;
|
return ParsedCallArgumentSlot::NoMoreArguments;
|
||||||
}
|
}
|
||||||
Some(Token::Comma) => {
|
Some(Token::Comma) => {
|
||||||
// In `f(,x)`, the leading comma both creates the omitted first
|
// We handle special case of first empty argument by *not*
|
||||||
// slot and separates it from `x`, so the first slot must not
|
// consuming first comma (it will be consumed together with
|
||||||
// consume it.
|
// the second argument).
|
||||||
if state.has_parsed_any_argument_slots() {
|
//
|
||||||
|
// 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();
|
self.advance();
|
||||||
}
|
}
|
||||||
if self.is_at_call_argument_boundary() {
|
// This `if`'s body is guaranteed to run if we've skipped
|
||||||
state.parsed_argument_slot_count += 1;
|
// `advance()` above.
|
||||||
return ParsedArgumentSlot::Argument(None);
|
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 argument = self.parse_expression();
|
||||||
let mut 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 =
|
ParsedCallArgumentSlot::Argument(Some(argument))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
/// Returns all parsed argument slots, preserving omitted arguments
|
||||||
/// the recovered position.
|
/// as `None`.
|
||||||
#[must_use]
|
fn parse_call_argument_list(
|
||||||
fn recover_after_missing_function_call_argument_separator(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
callee_end_position: TokenPosition,
|
|
||||||
left_parenthesis_position: TokenPosition,
|
left_parenthesis_position: TokenPosition,
|
||||||
previous_argument_span: Option<lexer::TokenSpan>,
|
) -> ArenaVec<'arena, Option<ExpressionRef<'src, 'arena>>> {
|
||||||
) -> bool {
|
let mut argument_list = ArenaVec::new_in(self.arena);
|
||||||
if self.next_token_definitely_cannot_start_expression() {
|
|
||||||
let unexpected_token_position = self.peek_position_or_eof();
|
let mut call_state = CallArgumentListParseState::new();
|
||||||
let mut error = self
|
//let mut old_position = self.peek_position_or_eof();
|
||||||
.make_error_at(
|
// This caused infinite loop? (on eof?) what?
|
||||||
ParseErrorKind::FunctionCallUnexpectedTokenInArgumentList,
|
while let ParsedCallArgumentSlot::Argument(argument) =
|
||||||
unexpected_token_position,
|
self.parse_call_argument_slot(&mut call_state)
|
||||||
)
|
{
|
||||||
.widen_error_span_from(left_parenthesis_position)
|
argument_list.push(argument);
|
||||||
.sync_error_until(self, SyncLevel::ListSeparator)
|
// TODO: ensure progress here shouldn't be necessary actually
|
||||||
.blame_token(unexpected_token_position)
|
//self.ensure_forward_progress(old_position);
|
||||||
.related_token("callee", callee_end_position)
|
//old_position = self.peek_position_or_eof();
|
||||||
.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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
argument_list
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the current token is a call-argument boundary.
|
/// Returns whether the current lookahead token ends the current call
|
||||||
#[must_use]
|
/// argument slot.
|
||||||
fn is_at_call_argument_boundary(&mut self) -> bool {
|
///
|
||||||
|
/// 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!(
|
matches!(
|
||||||
self.peek_token(),
|
self.peek_token(),
|
||||||
None | Some(Token::Comma | Token::RightParenthesis)
|
Some(Token::Comma | Token::RightParenthesis)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -243,10 +243,7 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(position) = self.peek_position() {
|
while self.peek_position_or_eof() < target {
|
||||||
if position >= target {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
self.advance();
|
self.advance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,10 +279,7 @@ impl<'src, 'arena> Parser<'src, 'arena> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(position) = self.peek_position() {
|
while self.peek_position_or_eof() < target {
|
||||||
if position >= target {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
self.advance();
|
self.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ use rottlib::parser::Parser;
|
|||||||
mod block_items;
|
mod block_items;
|
||||||
mod control_flow_expressions;
|
mod control_flow_expressions;
|
||||||
mod primary_expressions;
|
mod primary_expressions;
|
||||||
mod selector_expressions;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct ExpectedLabel {
|
pub(super) struct ExpectedLabel {
|
||||||
|
|||||||
@ -35,10 +35,6 @@ pub(super) const P0002_FIXTURES: &[Fixture] = &[
|
|||||||
label: "files/P0002_04.uc",
|
label: "files/P0002_04.uc",
|
||||||
source: "a * * *",
|
source: "a * * *",
|
||||||
},
|
},
|
||||||
Fixture {
|
|
||||||
label: "files/P0002_05.uc",
|
|
||||||
source: "new(Outer, Name, 7 +) SomeClass"
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(super) const P0003_FIXTURES: &[Fixture] = &[
|
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_02.uc").unwrap().len(), 1);
|
||||||
assert_eq!(runs.get("files/P0002_03.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_04.uc").unwrap().len(), 1);
|
||||||
assert_eq!(runs.get("files/P0002_05.uc").unwrap().len(), 1);
|
|
||||||
|
|
||||||
assert_diagnostic(
|
assert_diagnostic(
|
||||||
&runs.get_any("files/P0002_01.uc"),
|
&runs.get_any("files/P0002_01.uc"),
|
||||||
@ -409,25 +404,6 @@ fn check_p0002_fixtures() {
|
|||||||
notes: &[],
|
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]
|
#[test]
|
||||||
@ -689,7 +665,7 @@ fn check_p0005_fixtures() {
|
|||||||
code: Some("P0005"),
|
code: Some("P0005"),
|
||||||
primary_label: Some(ExpectedLabel {
|
primary_label: Some(ExpectedLabel {
|
||||||
span: TokenSpan {
|
span: TokenSpan {
|
||||||
start: TokenPosition(14),
|
start: TokenPosition(9),
|
||||||
end: TokenPosition(14),
|
end: TokenPosition(14),
|
||||||
},
|
},
|
||||||
message: "unexpected `>`",
|
message: "unexpected `>`",
|
||||||
@ -723,7 +699,7 @@ fn check_p0005_fixtures() {
|
|||||||
code: Some("P0005"),
|
code: Some("P0005"),
|
||||||
primary_label: Some(ExpectedLabel {
|
primary_label: Some(ExpectedLabel {
|
||||||
span: TokenSpan {
|
span: TokenSpan {
|
||||||
start: TokenPosition(10),
|
start: TokenPosition(5),
|
||||||
end: TokenPosition(10),
|
end: TokenPosition(10),
|
||||||
},
|
},
|
||||||
message: "unexpected `>`",
|
message: "unexpected `>`",
|
||||||
|
|||||||
@ -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: &[],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user