parser: support more recovery test cases

This commit is contained in:
Andrew Kelley
2021-02-21 17:37:10 -07:00
parent 15603f403c
commit 866f7dc7d6
3 changed files with 141 additions and 116 deletions

View File

@@ -205,6 +205,9 @@ pub const Tree = struct {
token_tags[parse_error.token].symbol(),
});
},
.expected_pub_item => {
return stream.writeAll("expected function or variable declaration after pub");
},
.expected_return_type => {
return stream.print("expected return type expression, found '{s}'", .{
token_tags[parse_error.token].symbol(),
@@ -265,6 +268,9 @@ pub const Tree = struct {
.invalid_align => {
return stream.writeAll("alignment not allowed on arrays");
},
.invalid_and => {
return stream.writeAll("`&&` is invalid; note that `and` is boolean AND");
},
.invalid_bit_range => {
return stream.writeAll("bit range not allowed on slices and arrays");
},
@@ -2316,6 +2322,7 @@ pub const Error = struct {
expected_param_list,
expected_prefix_expr,
expected_primary_type_expr,
expected_pub_item,
expected_return_type,
expected_semi_or_else,
expected_semi_or_lbrace,
@@ -2330,6 +2337,7 @@ pub const Error = struct {
extra_const_qualifier,
extra_volatile_qualifier,
invalid_align,
invalid_and,
invalid_bit_range,
invalid_token,
same_line_doc_comment,

View File

@@ -423,7 +423,7 @@ const Parser = struct {
while (true) {
const tok = p.nextToken();
switch (p.token_tags[tok]) {
// any of these can start a new top level declaration
// Any of these can start a new top level declaration.
.keyword_test,
.keyword_comptime,
.keyword_pub,
@@ -436,13 +436,18 @@ const Parser = struct {
.keyword_const,
.keyword_var,
.keyword_fn,
.identifier,
=> {
if (level == 0) {
p.tok_i -= 1;
return;
}
},
.identifier => {
if (p.token_tags[tok + 1] == .comma and level == 0) {
p.tok_i -= 1;
return;
}
},
.comma, .semicolon => {
// this decl was likely meant to end here
if (level == 0) {
@@ -531,10 +536,13 @@ const Parser = struct {
fn expectTopLevelDecl(p: *Parser) !Node.Index {
const extern_export_inline_token = p.nextToken();
var expect_fn: bool = false;
var exported: bool = false;
var expect_var_or_fn: bool = false;
switch (p.token_tags[extern_export_inline_token]) {
.keyword_extern => _ = p.eatToken(.string_literal),
.keyword_export => exported = true,
.keyword_extern => {
_ = p.eatToken(.string_literal);
expect_var_or_fn = true;
},
.keyword_export => expect_var_or_fn = true,
.keyword_inline, .keyword_noinline => expect_fn = true,
else => p.tok_i -= 1,
}
@@ -580,11 +588,12 @@ const Parser = struct {
if (thread_local_token != null) {
return p.fail(.expected_var_decl);
}
if (exported) {
if (expect_var_or_fn) {
return p.fail(.expected_var_decl_or_fn);
}
if (p.token_tags[p.tok_i] != .keyword_usingnamespace) {
return p.fail(.expected_pub_item);
}
return p.expectUsingNamespace();
}
@@ -599,7 +608,7 @@ const Parser = struct {
}
fn expectUsingNamespace(p: *Parser) !Node.Index {
const usingnamespace_token = try p.expectToken(.keyword_usingnamespace);
const usingnamespace_token = p.assertToken(.keyword_usingnamespace);
const expr = try p.expectExpr();
const semicolon_token = try p.expectToken(.semicolon);
return p.addNode(.{
@@ -1346,6 +1355,11 @@ const Parser = struct {
},
});
},
.invalid_ampersands => {
try p.warn(.invalid_and);
p.tok_i += 1;
return p.parseCompareExpr();
},
else => return res,
}
}
@@ -2283,10 +2297,12 @@ const Parser = struct {
if (node == 0) break;
res = node;
}
const lparen = (try p.expectTokenRecoverable(.l_paren)) orelse {
const lparen = p.nextToken();
if (p.token_tags[lparen] != .l_paren) {
p.tok_i -= 1;
try p.warn(.expected_param_list);
return res;
};
}
if (p.eatToken(.r_paren)) |_| {
return p.addNode(.{
.tag = .async_call_one,
@@ -3769,7 +3785,8 @@ const Parser = struct {
/// ExprList <- (Expr COMMA)* Expr?
fn parseBuiltinCall(p: *Parser) !Node.Index {
const builtin_token = p.assertToken(.builtin);
_ = (try p.expectTokenRecoverable(.l_paren)) orelse {
if (p.token_tags[p.nextToken()] != .l_paren) {
p.tok_i -= 1;
try p.warn(.expected_param_list);
// Pretend this was an identifier so we can continue parsing.
return p.addNode(.{
@@ -3780,7 +3797,7 @@ const Parser = struct {
.rhs = undefined,
},
});
};
}
if (p.eatToken(.r_paren)) |_| {
return p.addNode(.{
.tag = .builtin_call_two,
@@ -4015,6 +4032,7 @@ const Parser = struct {
fn expectToken(p: *Parser, tag: Token.Tag) Error!TokenIndex {
const token = p.nextToken();
if (p.token_tags[token] != tag) {
p.tok_i -= 1; // Go back so that we can recover properly.
return p.failMsg(.{
.tag = .expected_token,
.token = token,

View File

@@ -3579,7 +3579,7 @@ test "zig fmt: file ends with struct field" {
// \\
// , &[_]Error{
// .expected_expr,
// .ExpectedVarDeclOrFn,
// .expected_var_decl_or_fn,
// });
//}
@@ -4070,24 +4070,24 @@ test "recovery: block statements" {
});
}
//test "recovery: missing comma" {
// try testError(
// \\test "" {
// \\ switch (foo) {
// \\ 2 => {}
// \\ 3 => {}
// \\ else => {
// \\ foo && bar +;
// \\ }
// \\ }
// \\}
// , &[_]Error{
// .expected_token,
// .expected_token,
// .invalid_and,
// .invalid_token,
// });
//}
test "recovery: missing comma" {
try testError(
\\test "" {
\\ switch (foo) {
\\ 2 => {}
\\ 3 => {}
\\ else => {
\\ foo && bar +;
\\ }
\\ }
\\}
, &[_]Error{
.expected_token,
.expected_token,
.invalid_and,
.invalid_token,
});
}
test "recovery: extra qualifier" {
try testError(
@@ -4099,94 +4099,93 @@ test "recovery: extra qualifier" {
});
}
//test "recovery: missing return type" {
// try testError(
// \\fn foo() {
// \\ a && b;
// \\}
// \\test ""
// , &[_]Error{
// .ExpectedReturnType,
// .invalid_and,
// .expected_block,
// });
//}
test "recovery: missing return type" {
try testError(
\\fn foo() {
\\ a && b;
\\}
\\test ""
, &[_]Error{
.expected_return_type,
.invalid_and,
.expected_block,
});
}
//test "recovery: continue after invalid decl" {
// try testError(
// \\fn foo {
// \\ inline;
// \\}
// \\pub test "" {
// \\ async a && b;
// \\}
// , &[_]Error{
// .expected_token,
// .ExpectedPubItem,
// .ExpectedParamList,
// .invalid_and,
// });
// try testError(
// \\threadlocal test "" {
// \\ @a && b;
// \\}
// , &[_]Error{
// .ExpectedVarDecl,
// .ExpectedParamList,
// .invalid_and,
// });
//}
test "recovery: continue after invalid decl" {
try testError(
\\fn foo {
\\ inline;
\\}
\\pub test "" {
\\ async a && b;
\\}
, &[_]Error{
.expected_token,
.expected_pub_item,
.expected_param_list,
.invalid_and,
});
try testError(
\\threadlocal test "" {
\\ @a && b;
\\}
, &[_]Error{
.expected_var_decl,
.expected_param_list,
.invalid_and,
});
}
//test "recovery: invalid extern/inline" {
// try testError(
// \\inline test "" { a && b; }
// , &[_]Error{
// .ExpectedFn,
// .invalid_and,
// });
// try testError(
// \\extern "" test "" { a && b; }
// , &[_]Error{
// .ExpectedVarDeclOrFn,
// .invalid_and,
// });
//}
test "recovery: invalid extern/inline" {
try testError(
\\inline test "" { a && b; }
, &[_]Error{
.expected_fn,
.invalid_and,
});
try testError(
\\extern "" test "" { a && b; }
, &[_]Error{
.expected_var_decl_or_fn,
.invalid_and,
});
}
//test "recovery: missing semicolon" {
// try testError(
// \\test "" {
// \\ comptime a && b
// \\ c && d
// \\ @foo
// \\}
// , &[_]Error{
// .invalid_and,
// .expected_token,
// .invalid_and,
// .expected_token,
// .ExpectedParamList,
// .expected_token,
// });
//}
test "recovery: missing semicolon" {
try testError(
\\test "" {
\\ comptime a && b
\\ c && d
\\ @foo
\\}
, &[_]Error{
.invalid_and,
.expected_token,
.invalid_and,
.expected_token,
.expected_param_list,
.expected_token,
});
}
//test "recovery: invalid container members" {
// try testError(
// \\usingnamespace;
// \\foo+
// \\bar@,
// \\while (a == 2) { test "" {}}
// \\test "" {
// \\ a && b
// \\}
// , &[_]Error{
// .expected_expr,
// .expected_token,
// .expected_token,
// .expected_container_members,
// .invalid_and,
// .expected_token,
// });
//}
test "recovery: invalid container members" {
try testError(
\\usingnamespace;
\\foo+
\\bar@,
\\while (a == 2) { test "" {}}
\\test "" {
\\ a && b
\\}
, &[_]Error{
.expected_expr,
.expected_token,
.expected_container_members,
.invalid_and,
.expected_token,
});
}
//test "recovery: invalid parameter" {
// try testError(