wsgl parser: support multiple error messages
Use synchronization tokens to ensure the parser can resynchronize on error. Bug: tint:282 Change-Id: I8bb033f8a723eb8f2bc029e1ffc8350174c964e2 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/32284 Commit-Queue: dan sinclair <dsinclair@chromium.org> Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
parent
ef486bf0e4
commit
af8091e353
1
BUILD.gn
1
BUILD.gn
|
@ -943,6 +943,7 @@ source_set("tint_unittests_wgsl_reader_src") {
|
||||||
"src/reader/wgsl/parser_impl_elseif_stmt_test.cc",
|
"src/reader/wgsl/parser_impl_elseif_stmt_test.cc",
|
||||||
"src/reader/wgsl/parser_impl_equality_expression_test.cc",
|
"src/reader/wgsl/parser_impl_equality_expression_test.cc",
|
||||||
"src/reader/wgsl/parser_impl_error_msg_test.cc",
|
"src/reader/wgsl/parser_impl_error_msg_test.cc",
|
||||||
|
"src/reader/wgsl/parser_impl_error_resync_test.cc",
|
||||||
"src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc",
|
"src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc",
|
||||||
"src/reader/wgsl/parser_impl_for_stmt_test.cc",
|
"src/reader/wgsl/parser_impl_for_stmt_test.cc",
|
||||||
"src/reader/wgsl/parser_impl_function_decl_test.cc",
|
"src/reader/wgsl/parser_impl_function_decl_test.cc",
|
||||||
|
|
|
@ -479,6 +479,7 @@ if(${TINT_BUILD_WGSL_READER})
|
||||||
reader/wgsl/parser_impl_elseif_stmt_test.cc
|
reader/wgsl/parser_impl_elseif_stmt_test.cc
|
||||||
reader/wgsl/parser_impl_equality_expression_test.cc
|
reader/wgsl/parser_impl_equality_expression_test.cc
|
||||||
reader/wgsl/parser_impl_error_msg_test.cc
|
reader/wgsl/parser_impl_error_msg_test.cc
|
||||||
|
reader/wgsl/parser_impl_error_resync_test.cc
|
||||||
reader/wgsl/parser_impl_exclusive_or_expression_test.cc
|
reader/wgsl/parser_impl_exclusive_or_expression_test.cc
|
||||||
reader/wgsl/parser_impl_for_stmt_test.cc
|
reader/wgsl/parser_impl_for_stmt_test.cc
|
||||||
reader/wgsl/parser_impl_function_decl_test.cc
|
reader/wgsl/parser_impl_function_decl_test.cc
|
||||||
|
|
|
@ -83,7 +83,11 @@ using Maybe = ParserImpl::Maybe<T>;
|
||||||
/// Controls the maximum number of times we'll call into the const_expr function
|
/// Controls the maximum number of times we'll call into the const_expr function
|
||||||
/// from itself. This is to guard against stack overflow when there is an
|
/// from itself. This is to guard against stack overflow when there is an
|
||||||
/// excessive number of type constructors inside the const_expr.
|
/// excessive number of type constructors inside the const_expr.
|
||||||
uint32_t kMaxConstExprDepth = 128;
|
constexpr uint32_t kMaxConstExprDepth = 128;
|
||||||
|
|
||||||
|
/// The maximum number of tokens to look ahead to try and sync the
|
||||||
|
/// parser on error.
|
||||||
|
constexpr size_t const kMaxResynchronizeLookahead = 32;
|
||||||
|
|
||||||
ast::Builtin ident_to_builtin(const std::string& str) {
|
ast::Builtin ident_to_builtin(const std::string& str) {
|
||||||
if (str == "position") {
|
if (str == "position") {
|
||||||
|
@ -122,6 +126,38 @@ bool is_decoration(Token t) {
|
||||||
t.IsOffset();
|
t.IsOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enter-exit counters for block token types.
|
||||||
|
/// Used by sync_to() to skip over closing block tokens that were opened during
|
||||||
|
/// the forward scan.
|
||||||
|
struct BlockCounters {
|
||||||
|
int attrs = 0; // [[ ]]
|
||||||
|
int brace = 0; // { }
|
||||||
|
int bracket = 0; // [ ]
|
||||||
|
int paren = 0; // ( )
|
||||||
|
|
||||||
|
/// @return the current enter-exit depth for the given block token type. If
|
||||||
|
/// |t| is not a block token type, then 0 is always returned.
|
||||||
|
int consume(const Token& t) {
|
||||||
|
if (t.Is(Token::Type::kAttrLeft))
|
||||||
|
return attrs++;
|
||||||
|
if (t.Is(Token::Type::kAttrRight))
|
||||||
|
return attrs--;
|
||||||
|
if (t.Is(Token::Type::kBraceLeft))
|
||||||
|
return brace++;
|
||||||
|
if (t.Is(Token::Type::kBraceRight))
|
||||||
|
return brace--;
|
||||||
|
if (t.Is(Token::Type::kBracketLeft))
|
||||||
|
return bracket++;
|
||||||
|
if (t.Is(Token::Type::kBracketRight))
|
||||||
|
return bracket--;
|
||||||
|
if (t.Is(Token::Type::kParenLeft))
|
||||||
|
return paren++;
|
||||||
|
if (t.Is(Token::Type::kParenRight))
|
||||||
|
return paren--;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ParserImpl::ParserImpl(Context* ctx, Source::File const* file)
|
ParserImpl::ParserImpl(Context* ctx, Source::File const* file)
|
||||||
|
@ -198,10 +234,8 @@ bool ParserImpl::Parse() {
|
||||||
// translation_unit
|
// translation_unit
|
||||||
// : global_decl* EOF
|
// : global_decl* EOF
|
||||||
void ParserImpl::translation_unit() {
|
void ParserImpl::translation_unit() {
|
||||||
while (!peek().IsEof()) {
|
while (!peek().IsEof() && synchronized_) {
|
||||||
auto decl = expect_global_decl();
|
expect_global_decl();
|
||||||
if (decl.errored)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(module_.IsValid());
|
assert(module_.IsValid());
|
||||||
|
@ -218,14 +252,15 @@ Expect<bool> ParserImpl::expect_global_decl() {
|
||||||
if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF))
|
if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
auto decos = decoration_list();
|
bool errored = false;
|
||||||
|
|
||||||
// FUDGE - Abort early if we enter with an error state to avoid accumulating
|
auto decos = decoration_list();
|
||||||
// multiple error messages.
|
if (decos.errored)
|
||||||
// TODO(ben-clayton) - remove this once resynchronization is implemented.
|
errored = true;
|
||||||
if (has_error())
|
if (!synchronized_)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
|
||||||
|
auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<bool> {
|
||||||
auto gv = global_variable_decl(decos.value);
|
auto gv = global_variable_decl(decos.value);
|
||||||
if (gv.errored)
|
if (gv.errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
@ -238,9 +273,9 @@ Expect<bool> ParserImpl::expect_global_decl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto gc = global_constant_decl();
|
auto gc = global_constant_decl();
|
||||||
if (gc.errored) {
|
if (gc.errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
|
||||||
if (gc.matched) {
|
if (gc.matched) {
|
||||||
if (!expect("constant declaration", Token::Type::kSemicolon))
|
if (!expect("constant declaration", Token::Type::kSemicolon))
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
@ -275,18 +310,29 @@ Expect<bool> ParserImpl::expect_global_decl() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Failure::kNoMatch;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (decl.errored)
|
||||||
|
errored = true;
|
||||||
|
if (decl.matched)
|
||||||
|
return true;
|
||||||
|
|
||||||
auto func = function_decl(decos.value);
|
auto func = function_decl(decos.value);
|
||||||
if (func.errored)
|
if (func.errored)
|
||||||
return Failure::kErrored;
|
errored = true;
|
||||||
if (func.matched) {
|
if (func.matched) {
|
||||||
module_.AddFunction(std::move(func.value));
|
module_.AddFunction(std::move(func.value));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errored)
|
||||||
|
return Failure::kErrored;
|
||||||
|
|
||||||
if (decos.value.size() > 0) {
|
if (decos.value.size() > 0) {
|
||||||
add_error(peek(), "expected declaration after decorations");
|
add_error(next(), "expected declaration after decorations");
|
||||||
} else {
|
} else {
|
||||||
add_error(peek(), "unexpected token");
|
add_error(next(), "unexpected token");
|
||||||
}
|
}
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
@ -1027,10 +1073,6 @@ Maybe<std::unique_ptr<ast::type::StructType>> ParserImpl::struct_decl(
|
||||||
if (!match(Token::Type::kStruct))
|
if (!match(Token::Type::kStruct))
|
||||||
return Failure::kNoMatch;
|
return Failure::kNoMatch;
|
||||||
|
|
||||||
auto struct_decos = cast_decorations<ast::StructDecoration>(decos);
|
|
||||||
if (struct_decos.errored)
|
|
||||||
return Failure::kErrored;
|
|
||||||
|
|
||||||
auto name = expect_ident("struct declaration");
|
auto name = expect_ident("struct declaration");
|
||||||
if (name.errored)
|
if (name.errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
@ -1039,6 +1081,10 @@ Maybe<std::unique_ptr<ast::type::StructType>> ParserImpl::struct_decl(
|
||||||
if (body.errored)
|
if (body.errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
|
||||||
|
auto struct_decos = cast_decorations<ast::StructDecoration>(decos);
|
||||||
|
if (struct_decos.errored)
|
||||||
|
return Failure::kErrored;
|
||||||
|
|
||||||
return std::make_unique<ast::type::StructType>(
|
return std::make_unique<ast::type::StructType>(
|
||||||
name.value,
|
name.value,
|
||||||
std::make_unique<ast::Struct>(source, std::move(struct_decos.value),
|
std::make_unique<ast::Struct>(source, std::move(struct_decos.value),
|
||||||
|
@ -1050,19 +1096,31 @@ Maybe<std::unique_ptr<ast::type::StructType>> ParserImpl::struct_decl(
|
||||||
Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() {
|
Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() {
|
||||||
return expect_brace_block(
|
return expect_brace_block(
|
||||||
"struct declaration", [&]() -> Expect<ast::StructMemberList> {
|
"struct declaration", [&]() -> Expect<ast::StructMemberList> {
|
||||||
|
bool errored = false;
|
||||||
|
|
||||||
ast::StructMemberList members;
|
ast::StructMemberList members;
|
||||||
|
|
||||||
while (!peek().IsBraceRight() && !peek().IsEof()) {
|
while (synchronized_ && !peek().IsBraceRight() && !peek().IsEof()) {
|
||||||
|
auto member =
|
||||||
|
sync(Token::Type::kSemicolon,
|
||||||
|
[&]() -> Expect<std::unique_ptr<ast::StructMember>> {
|
||||||
auto decos = decoration_list();
|
auto decos = decoration_list();
|
||||||
if (decos.errored)
|
if (decos.errored)
|
||||||
|
errored = true;
|
||||||
|
if (!synchronized_)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
return expect_struct_member(decos.value);
|
||||||
|
});
|
||||||
|
|
||||||
auto mem = expect_struct_member(decos.value);
|
if (member.errored) {
|
||||||
if (mem.errored)
|
errored = true;
|
||||||
return Failure::kErrored;
|
} else {
|
||||||
|
members.push_back(std::move(member.value));
|
||||||
members.push_back(std::move(mem.value));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errored)
|
||||||
|
return Failure::kErrored;
|
||||||
|
|
||||||
return members;
|
return members;
|
||||||
});
|
});
|
||||||
|
@ -1072,20 +1130,6 @@ Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() {
|
||||||
// : struct_member_decoration_decl+ variable_ident_decl SEMICOLON
|
// : struct_member_decoration_decl+ variable_ident_decl SEMICOLON
|
||||||
Expect<std::unique_ptr<ast::StructMember>> ParserImpl::expect_struct_member(
|
Expect<std::unique_ptr<ast::StructMember>> ParserImpl::expect_struct_member(
|
||||||
ast::DecorationList& decos) {
|
ast::DecorationList& decos) {
|
||||||
// FUDGE - Abort early if we enter with an error state to avoid accumulating
|
|
||||||
// multiple error messages. This is a work around for the unit tests that
|
|
||||||
// call:
|
|
||||||
// auto decos = p->decoration_list();
|
|
||||||
// auto m = p->expect_struct_member(decos);
|
|
||||||
// ... and expect a single error message due to bad decorations.
|
|
||||||
// While expect_struct_body_decl() aborts after checking for decoration parse
|
|
||||||
// errors (and so these tests do not currently reflect full-parse behaviour),
|
|
||||||
// they do test the long-term desired behavior where the parser can
|
|
||||||
// resynchronize at the ']]'.
|
|
||||||
// TODO(ben-clayton) - remove this once resynchronization is implemented.
|
|
||||||
if (has_error())
|
|
||||||
return Failure::kErrored;
|
|
||||||
|
|
||||||
auto decl = expect_variable_ident_decl("struct member");
|
auto decl = expect_variable_ident_decl("struct member");
|
||||||
if (decl.errored)
|
if (decl.errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
@ -1106,19 +1150,34 @@ Expect<std::unique_ptr<ast::StructMember>> ParserImpl::expect_struct_member(
|
||||||
Maybe<std::unique_ptr<ast::Function>> ParserImpl::function_decl(
|
Maybe<std::unique_ptr<ast::Function>> ParserImpl::function_decl(
|
||||||
ast::DecorationList& decos) {
|
ast::DecorationList& decos) {
|
||||||
auto f = function_header();
|
auto f = function_header();
|
||||||
if (f.errored)
|
if (f.errored) {
|
||||||
|
if (sync_to(Token::Type::kBraceLeft, /* consume: */ false)) {
|
||||||
|
// There were errors in the function header, but the parser has managed to
|
||||||
|
// resynchronize with the opening brace. As there's no outer
|
||||||
|
// synchronization token for function declarations, attempt to parse the
|
||||||
|
// function body. The AST isn't used as we've already errored, but this
|
||||||
|
// catches any errors inside the body, and can help keep the parser in
|
||||||
|
// sync.
|
||||||
|
expect_body_stmt();
|
||||||
|
}
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
}
|
||||||
if (!f.matched)
|
if (!f.matched)
|
||||||
return Failure::kNoMatch;
|
return Failure::kNoMatch;
|
||||||
|
|
||||||
|
bool errored = false;
|
||||||
|
|
||||||
auto func_decos = cast_decorations<ast::FunctionDecoration>(decos);
|
auto func_decos = cast_decorations<ast::FunctionDecoration>(decos);
|
||||||
if (func_decos.errored)
|
if (func_decos.errored)
|
||||||
return Failure::kErrored;
|
errored = true;
|
||||||
|
|
||||||
f->set_decorations(std::move(func_decos.value));
|
f->set_decorations(std::move(func_decos.value));
|
||||||
|
|
||||||
auto body = expect_body_stmt();
|
auto body = expect_body_stmt();
|
||||||
if (body.errored)
|
if (body.errored)
|
||||||
|
errored = true;
|
||||||
|
|
||||||
|
if (errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
|
||||||
f->set_body(std::move(body.value));
|
f->set_body(std::move(body.value));
|
||||||
|
@ -1143,23 +1202,34 @@ Maybe<std::unique_ptr<ast::Function>> ParserImpl::function_header() {
|
||||||
return Failure::kNoMatch;
|
return Failure::kNoMatch;
|
||||||
|
|
||||||
const char* use = "function declaration";
|
const char* use = "function declaration";
|
||||||
|
bool errored = false;
|
||||||
|
|
||||||
auto name = expect_ident(use);
|
auto name = expect_ident(use);
|
||||||
if (name.errored)
|
if (name.errored) {
|
||||||
|
errored = true;
|
||||||
|
if (!sync_to(Token::Type::kParenLeft, /* consume: */ false))
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
}
|
||||||
|
|
||||||
auto params = expect_paren_block(use, [&] { return expect_param_list(); });
|
auto params = expect_paren_block(use, [&] { return expect_param_list(); });
|
||||||
if (params.errored)
|
if (params.errored) {
|
||||||
|
errored = true;
|
||||||
|
if (!synchronized_)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
}
|
||||||
|
|
||||||
if (!expect(use, Token::Type::kArrow))
|
if (!expect(use, Token::Type::kArrow))
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
|
||||||
auto type = function_type_decl();
|
auto type = function_type_decl();
|
||||||
if (type.errored)
|
if (type.errored) {
|
||||||
return Failure::kErrored;
|
errored = true;
|
||||||
if (!type.matched)
|
} else if (!type.matched) {
|
||||||
return add_error(peek(), "unable to determine function return type");
|
return add_error(peek(), "unable to determine function return type");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errored)
|
||||||
|
return Failure::kErrored;
|
||||||
|
|
||||||
return std::make_unique<ast::Function>(source, name.value,
|
return std::make_unique<ast::Function>(source, name.value,
|
||||||
std::move(params.value), type.value);
|
std::move(params.value), type.value);
|
||||||
|
@ -1252,17 +1322,22 @@ Expect<std::unique_ptr<ast::Expression>> ParserImpl::expect_paren_rhs_stmt() {
|
||||||
// statements
|
// statements
|
||||||
// : statement*
|
// : statement*
|
||||||
Expect<std::unique_ptr<ast::BlockStatement>> ParserImpl::expect_statements() {
|
Expect<std::unique_ptr<ast::BlockStatement>> ParserImpl::expect_statements() {
|
||||||
|
bool errored = false;
|
||||||
auto ret = std::make_unique<ast::BlockStatement>();
|
auto ret = std::make_unique<ast::BlockStatement>();
|
||||||
|
|
||||||
for (;;) {
|
while (synchronized_) {
|
||||||
auto stmt = statement();
|
auto stmt = statement();
|
||||||
if (stmt.errored)
|
if (stmt.errored) {
|
||||||
return Failure::kErrored;
|
errored = true;
|
||||||
if (!stmt.matched)
|
} else if (stmt.matched) {
|
||||||
break;
|
|
||||||
|
|
||||||
ret->append(std::move(stmt.value));
|
ret->append(std::move(stmt.value));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errored)
|
||||||
|
return Failure::kErrored;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1287,9 +1362,10 @@ Maybe<std::unique_ptr<ast::Statement>> ParserImpl::statement() {
|
||||||
// Skip empty statements
|
// Skip empty statements
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-block statments all end in a semi-colon.
|
// Non-block statments that error can resynchronize on semicolon.
|
||||||
// TODO(bclayton): We can use this property to synchronize on error.
|
auto stmt =
|
||||||
auto stmt = non_block_statement();
|
sync(Token::Type::kSemicolon, [&] { return non_block_statement(); });
|
||||||
|
|
||||||
if (stmt.errored)
|
if (stmt.errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
if (stmt.matched)
|
if (stmt.matched)
|
||||||
|
@ -1401,6 +1477,7 @@ Maybe<std::unique_ptr<ast::ReturnStatement>> ParserImpl::return_stmt() {
|
||||||
auto expr = logical_or_expression();
|
auto expr = logical_or_expression();
|
||||||
if (expr.errored)
|
if (expr.errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
|
||||||
// TODO(bclayton): Check matched?
|
// TODO(bclayton): Check matched?
|
||||||
return std::make_unique<ast::ReturnStatement>(source, std::move(expr.value));
|
return std::make_unique<ast::ReturnStatement>(source, std::move(expr.value));
|
||||||
}
|
}
|
||||||
|
@ -1540,16 +1617,20 @@ Maybe<std::unique_ptr<ast::SwitchStatement>> ParserImpl::switch_stmt() {
|
||||||
|
|
||||||
auto body = expect_brace_block("switch statement",
|
auto body = expect_brace_block("switch statement",
|
||||||
[&]() -> Expect<ast::CaseStatementList> {
|
[&]() -> Expect<ast::CaseStatementList> {
|
||||||
|
bool errored = false;
|
||||||
ast::CaseStatementList list;
|
ast::CaseStatementList list;
|
||||||
for (;;) {
|
while (synchronized_) {
|
||||||
auto stmt = switch_body();
|
auto stmt = switch_body();
|
||||||
if (stmt.errored)
|
if (stmt.errored) {
|
||||||
return Failure::kErrored;
|
errored = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!stmt.matched)
|
if (!stmt.matched)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
list.push_back(std::move(stmt.value));
|
list.push_back(std::move(stmt.value));
|
||||||
}
|
}
|
||||||
|
if (errored)
|
||||||
|
return Failure::kErrored;
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1630,11 +1711,8 @@ Expect<ast::CaseSelectorList> ParserImpl::expect_case_selectors() {
|
||||||
Maybe<std::unique_ptr<ast::BlockStatement>> ParserImpl::case_body() {
|
Maybe<std::unique_ptr<ast::BlockStatement>> ParserImpl::case_body() {
|
||||||
auto ret = std::make_unique<ast::BlockStatement>();
|
auto ret = std::make_unique<ast::BlockStatement>();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto t = peek();
|
Source source;
|
||||||
if (t.IsFallthrough()) {
|
if (match(Token::Type::kFallthrough, &source)) {
|
||||||
auto source = t.source();
|
|
||||||
next(); // Consume the peek
|
|
||||||
|
|
||||||
if (!expect("fallthrough statement", Token::Type::kSemicolon))
|
if (!expect("fallthrough statement", Token::Type::kSemicolon))
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
|
||||||
|
@ -2571,19 +2649,23 @@ ParserImpl::expect_const_expr_internal(uint32_t depth) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Maybe<ast::DecorationList> ParserImpl::decoration_list() {
|
Maybe<ast::DecorationList> ParserImpl::decoration_list() {
|
||||||
|
bool errored = false;
|
||||||
bool matched = false;
|
bool matched = false;
|
||||||
ast::DecorationList decos;
|
ast::DecorationList decos;
|
||||||
|
|
||||||
while (true) {
|
while (synchronized_) {
|
||||||
auto list = decoration_bracketed_list(decos);
|
auto list = decoration_bracketed_list(decos);
|
||||||
if (list.errored)
|
if (list.errored)
|
||||||
return Failure::kErrored;
|
errored = true;
|
||||||
if (!list.matched)
|
if (!list.matched)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
matched = true;
|
matched = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errored)
|
||||||
|
return Failure::kErrored;
|
||||||
|
|
||||||
if (!matched)
|
if (!matched)
|
||||||
return Failure::kNoMatch;
|
return Failure::kNoMatch;
|
||||||
|
|
||||||
|
@ -2591,6 +2673,8 @@ Maybe<ast::DecorationList> ParserImpl::decoration_list() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Maybe<bool> ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) {
|
Maybe<bool> ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) {
|
||||||
|
const char* use = "decoration list";
|
||||||
|
|
||||||
if (!match(Token::Type::kAttrLeft)) {
|
if (!match(Token::Type::kAttrLeft)) {
|
||||||
return Failure::kNoMatch;
|
return Failure::kNoMatch;
|
||||||
}
|
}
|
||||||
|
@ -2599,30 +2683,37 @@ Maybe<bool> ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) {
|
||||||
if (match(Token::Type::kAttrRight, &source))
|
if (match(Token::Type::kAttrRight, &source))
|
||||||
return add_error(source, "empty decoration list");
|
return add_error(source, "empty decoration list");
|
||||||
|
|
||||||
while (true) {
|
return sync(Token::Type::kAttrRight, [&]() -> Expect<bool> {
|
||||||
|
bool errored = false;
|
||||||
|
|
||||||
|
while (synchronized_) {
|
||||||
auto deco = expect_decoration();
|
auto deco = expect_decoration();
|
||||||
if (deco.errored)
|
if (deco.errored)
|
||||||
return Failure::kErrored;
|
errored = true;
|
||||||
|
|
||||||
decos.emplace_back(std::move(deco.value));
|
decos.emplace_back(std::move(deco.value));
|
||||||
|
|
||||||
if (match(Token::Type::kComma)) {
|
if (match(Token::Type::kComma))
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (is_decoration(peek())) {
|
if (is_decoration(peek())) {
|
||||||
// We have two decorations in a bracket without a separating comma.
|
// We have two decorations in a bracket without a separating comma.
|
||||||
// e.g. [[location(1) set(2)]]
|
// e.g. [[location(1) set(2)]]
|
||||||
// ^^^ expected comma
|
// ^^^ expected comma
|
||||||
expect("decoration list", Token::Type::kComma);
|
expect(use, Token::Type::kComma);
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expect("decoration list", Token::Type::kAttrRight))
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errored)
|
||||||
|
return Failure::kErrored;
|
||||||
|
|
||||||
|
if (!expect(use, Token::Type::kAttrRight))
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect<std::unique_ptr<ast::Decoration>> ParserImpl::expect_decoration() {
|
Expect<std::unique_ptr<ast::Decoration>> ParserImpl::expect_decoration() {
|
||||||
|
@ -2791,25 +2882,29 @@ bool ParserImpl::match(Token::Type tok, Source* source /*= nullptr*/) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ParserImpl::expect(const std::string& use, Token::Type tok) {
|
bool ParserImpl::expect(const std::string& use, Token::Type tok) {
|
||||||
auto t = next();
|
auto t = peek();
|
||||||
if (!t.Is(tok)) {
|
if (t.Is(tok)) {
|
||||||
|
next();
|
||||||
|
synchronized_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::stringstream err;
|
std::stringstream err;
|
||||||
err << "expected '" << Token::TypeToName(tok) << "'";
|
err << "expected '" << Token::TypeToName(tok) << "'";
|
||||||
if (!use.empty()) {
|
if (!use.empty()) {
|
||||||
err << " for " << use;
|
err << " for " << use;
|
||||||
}
|
}
|
||||||
add_error(t, err.str());
|
add_error(t, err.str());
|
||||||
|
synchronized_ = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect<int32_t> ParserImpl::expect_sint(const std::string& use) {
|
Expect<int32_t> ParserImpl::expect_sint(const std::string& use) {
|
||||||
auto t = next();
|
auto t = peek();
|
||||||
|
|
||||||
if (!t.IsSintLiteral())
|
if (!t.IsSintLiteral())
|
||||||
return add_error(t.source(), "expected signed integer literal", use);
|
return add_error(t.source(), "expected signed integer literal", use);
|
||||||
|
|
||||||
|
next();
|
||||||
return {t.to_i32(), t.source()};
|
return {t.to_i32(), t.source()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2837,12 +2932,15 @@ Expect<uint32_t> ParserImpl::expect_nonzero_positive_sint(
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect<std::string> ParserImpl::expect_ident(const std::string& use) {
|
Expect<std::string> ParserImpl::expect_ident(const std::string& use) {
|
||||||
auto t = next();
|
auto t = peek();
|
||||||
if (!t.IsIdentifier())
|
if (t.IsIdentifier()) {
|
||||||
return add_error(t.source(), "expected identifier", use);
|
synchronized_ = true;
|
||||||
|
next();
|
||||||
return {t.to_str(), t.source()};
|
return {t.to_str(), t.source()};
|
||||||
}
|
}
|
||||||
|
synchronized_ = false;
|
||||||
|
return add_error(t.source(), "expected identifier", use);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename F, typename T>
|
template <typename F, typename T>
|
||||||
T ParserImpl::expect_block(Token::Type start,
|
T ParserImpl::expect_block(Token::Type start,
|
||||||
|
@ -2852,14 +2950,18 @@ T ParserImpl::expect_block(Token::Type start,
|
||||||
if (!expect(use, start)) {
|
if (!expect(use, start)) {
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sync(end, [&]() -> T {
|
||||||
auto res = body();
|
auto res = body();
|
||||||
if (res.errored) {
|
|
||||||
|
if (res.errored)
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
|
||||||
if (!expect(use, end)) {
|
if (!expect(use, end))
|
||||||
return Failure::kErrored;
|
return Failure::kErrored;
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename F, typename T>
|
template <typename F, typename T>
|
||||||
|
@ -2880,6 +2982,64 @@ T ParserImpl::expect_lt_gt_block(const std::string& use, F&& body) {
|
||||||
std::forward<F>(body));
|
std::forward<F>(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename F, typename T>
|
||||||
|
T ParserImpl::sync(Token::Type tok, F&& body) {
|
||||||
|
sync_tokens_.push_back(tok);
|
||||||
|
|
||||||
|
auto result = body();
|
||||||
|
|
||||||
|
assert(sync_tokens_.back() == tok);
|
||||||
|
sync_tokens_.pop_back();
|
||||||
|
|
||||||
|
if (result.errored) {
|
||||||
|
sync_to(tok, /* consume: */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParserImpl::sync_to(Token::Type tok, bool consume) {
|
||||||
|
// Clear the synchronized state - gets set to true again on success.
|
||||||
|
synchronized_ = false;
|
||||||
|
|
||||||
|
BlockCounters counters;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kMaxResynchronizeLookahead; i++) {
|
||||||
|
auto t = peek(i);
|
||||||
|
if (counters.consume(t) > 0)
|
||||||
|
continue; // Nested block
|
||||||
|
if (!t.Is(tok) && !is_sync_token(t))
|
||||||
|
continue; // Not a synchronization point
|
||||||
|
|
||||||
|
// Synchronization point found.
|
||||||
|
|
||||||
|
// Skip any tokens we don't understand, bringing us to just before the
|
||||||
|
// resync point.
|
||||||
|
while (i-- > 0) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this synchronization token |tok|?
|
||||||
|
if (t.Is(tok)) {
|
||||||
|
if (consume)
|
||||||
|
next();
|
||||||
|
synchronized_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParserImpl::is_sync_token(const Token& t) const {
|
||||||
|
for (auto r : sync_tokens_) {
|
||||||
|
if (t.Is(r))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace wgsl
|
} // namespace wgsl
|
||||||
} // namespace reader
|
} // namespace reader
|
||||||
} // namespace tint
|
} // namespace tint
|
||||||
|
|
|
@ -606,31 +606,35 @@ class ParserImpl {
|
||||||
/// pointer, regardless of success or error
|
/// pointer, regardless of success or error
|
||||||
bool match(Token::Type tok, Source* source = nullptr);
|
bool match(Token::Type tok, Source* source = nullptr);
|
||||||
/// Errors if the next token is not equal to |tok|.
|
/// Errors if the next token is not equal to |tok|.
|
||||||
/// Always consumes the next token.
|
/// Consumes the next token on match.
|
||||||
|
/// expect() also updates |synchronized_|, setting it to `true` if the next
|
||||||
|
/// token is equal to |tok|, otherwise `false`.
|
||||||
/// @param use a description of what was being parsed if an error was raised.
|
/// @param use a description of what was being parsed if an error was raised.
|
||||||
/// @param tok the token to test against
|
/// @param tok the token to test against
|
||||||
/// @returns true if the next token equals |tok|.
|
/// @returns true if the next token equals |tok|.
|
||||||
bool expect(const std::string& use, Token::Type tok);
|
bool expect(const std::string& use, Token::Type tok);
|
||||||
/// Parses a signed integer from the next token in the stream, erroring if the
|
/// Parses a signed integer from the next token in the stream, erroring if the
|
||||||
/// next token is not a signed integer.
|
/// next token is not a signed integer.
|
||||||
/// Always consumes the next token.
|
/// Consumes the next token on match.
|
||||||
/// @param use a description of what was being parsed if an error was raised
|
/// @param use a description of what was being parsed if an error was raised
|
||||||
/// @returns the parsed integer.
|
/// @returns the parsed integer.
|
||||||
Expect<int32_t> expect_sint(const std::string& use);
|
Expect<int32_t> expect_sint(const std::string& use);
|
||||||
/// Parses a signed integer from the next token in the stream, erroring if
|
/// Parses a signed integer from the next token in the stream, erroring if
|
||||||
/// the next token is not a signed integer or is negative.
|
/// the next token is not a signed integer or is negative.
|
||||||
/// Always consumes the next token.
|
/// Consumes the next token if it is a signed integer (not necessarily
|
||||||
|
/// negative).
|
||||||
/// @param use a description of what was being parsed if an error was raised
|
/// @param use a description of what was being parsed if an error was raised
|
||||||
/// @returns the parsed integer.
|
/// @returns the parsed integer.
|
||||||
Expect<uint32_t> expect_positive_sint(const std::string& use);
|
Expect<uint32_t> expect_positive_sint(const std::string& use);
|
||||||
/// Parses a non-zero signed integer from the next token in the stream,
|
/// Parses a non-zero signed integer from the next token in the stream,
|
||||||
/// erroring if the next token is not a signed integer or is less than 1.
|
/// erroring if the next token is not a signed integer or is less than 1.
|
||||||
/// Always consumes the next token.
|
/// Consumes the next token if it is a signed integer (not necessarily
|
||||||
|
/// >= 1).
|
||||||
/// @param use a description of what was being parsed if an error was raised
|
/// @param use a description of what was being parsed if an error was raised
|
||||||
/// @returns the parsed integer.
|
/// @returns the parsed integer.
|
||||||
Expect<uint32_t> expect_nonzero_positive_sint(const std::string& use);
|
Expect<uint32_t> expect_nonzero_positive_sint(const std::string& use);
|
||||||
/// Errors if the next token is not an identifier.
|
/// Errors if the next token is not an identifier.
|
||||||
/// Always consumes the next token.
|
/// Consumes the next token on match.
|
||||||
/// @param use a description of what was being parsed if an error was raised
|
/// @param use a description of what was being parsed if an error was raised
|
||||||
/// @returns the parsed identifier.
|
/// @returns the parsed identifier.
|
||||||
Expect<std::string> expect_ident(const std::string& use);
|
Expect<std::string> expect_ident(const std::string& use);
|
||||||
|
@ -684,6 +688,45 @@ class ParserImpl {
|
||||||
template <typename F, typename T = ReturnType<F>>
|
template <typename F, typename T = ReturnType<F>>
|
||||||
T expect_lt_gt_block(const std::string& use, F&& body);
|
T expect_lt_gt_block(const std::string& use, F&& body);
|
||||||
|
|
||||||
|
/// sync() calls the function |func|, and attempts to resynchronize the parser
|
||||||
|
/// to the next found resynchronization token if |func| fails.
|
||||||
|
/// If the next found resynchronization token is |tok|, then sync will also
|
||||||
|
/// consume |tok|.
|
||||||
|
///
|
||||||
|
/// sync() will transiently add |tok| to the parser's stack of synchronization
|
||||||
|
/// tokens for the duration of the call to |func|. Once |func| returns, |tok|
|
||||||
|
/// is removed from the stack of resynchronization tokens. sync calls may be
|
||||||
|
/// nested, and so the number of resynchronization tokens is equal to the
|
||||||
|
/// number of |sync| calls in the current stack frame.
|
||||||
|
///
|
||||||
|
/// sync() updates |synchronized_|, setting it to `true` if the next
|
||||||
|
/// resynchronization token found was |tok|, otherwise `false`.
|
||||||
|
///
|
||||||
|
/// @param tok the token to attempt to synchronize the parser to if |func|
|
||||||
|
/// fails.
|
||||||
|
/// @param func a function or lambda with the signature: `Expect<Result>()` or
|
||||||
|
/// `Maybe<Result>()`.
|
||||||
|
/// @return the value returned by |func|.
|
||||||
|
template <typename F, typename T = ReturnType<F>>
|
||||||
|
T sync(Token::Type tok, F&& func);
|
||||||
|
/// sync_to() attempts to resynchronize the parser to the next found
|
||||||
|
/// resynchronization token or |tok| (whichever comes first).
|
||||||
|
///
|
||||||
|
/// Synchronization tokens are transiently defined by calls to |sync|.
|
||||||
|
///
|
||||||
|
/// sync_to() updates |synchronized_|, setting it to `true` if a
|
||||||
|
/// resynchronization token was found and it was |tok|, otherwise `false`.
|
||||||
|
///
|
||||||
|
/// @param tok the token to attempt to synchronize the parser to.
|
||||||
|
/// @param consume if true and the next found resynchronization token is
|
||||||
|
/// |tok| then sync_to() will also consume |tok|.
|
||||||
|
/// @return the state of |synchronized_|.
|
||||||
|
/// @see sync().
|
||||||
|
bool sync_to(Token::Type tok, bool consume);
|
||||||
|
/// @return true if |t| is in the stack of resynchronization tokens.
|
||||||
|
/// @see sync().
|
||||||
|
bool is_sync_token(const Token& t) const;
|
||||||
|
|
||||||
/// Downcasts all the decorations in |list| to the type |T|, raising a parser
|
/// Downcasts all the decorations in |list| to the type |T|, raising a parser
|
||||||
/// error if any of the decorations aren't of the type |T|.
|
/// error if any of the decorations aren't of the type |T|.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -712,6 +755,8 @@ class ParserImpl {
|
||||||
diag::List diags_;
|
diag::List diags_;
|
||||||
std::unique_ptr<Lexer> lexer_;
|
std::unique_ptr<Lexer> lexer_;
|
||||||
std::deque<Token> token_queue_;
|
std::deque<Token> token_queue_;
|
||||||
|
bool synchronized_ = true;
|
||||||
|
std::vector<Token::Type> sync_tokens_;
|
||||||
std::unordered_map<std::string, ast::type::Type*> registered_constructs_;
|
std::unordered_map<std::string, ast::type::Type*> registered_constructs_;
|
||||||
ast::Module module_;
|
ast::Module module_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
// Copyright 2020 The Tint Authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "src/reader/wgsl/parser_impl.h"
|
||||||
|
#include "src/reader/wgsl/parser_impl_test_helper.h"
|
||||||
|
|
||||||
|
namespace tint {
|
||||||
|
namespace reader {
|
||||||
|
namespace wgsl {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ParserImplErrorResyncTest : public ParserImplTest {};
|
||||||
|
|
||||||
|
#define EXPECT(SOURCE, EXPECTED) \
|
||||||
|
do { \
|
||||||
|
std::string source = SOURCE; \
|
||||||
|
std::string expected = EXPECTED; \
|
||||||
|
auto* p = parser(source); \
|
||||||
|
EXPECT_EQ(false, p->Parse()); \
|
||||||
|
EXPECT_EQ(true, p->diagnostics().contains_errors()); \
|
||||||
|
EXPECT_EQ(expected, diag::Formatter().format(p->diagnostics())); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
TEST_F(ParserImplErrorResyncTest, BadFunctionDecls) {
|
||||||
|
EXPECT(R"(
|
||||||
|
fn .() -> . {}
|
||||||
|
fn x(.) . {}
|
||||||
|
[[.,.]] fn -> {}
|
||||||
|
fn good() -> void {}
|
||||||
|
)",
|
||||||
|
"test.wgsl:2:4 error: expected identifier for function declaration\n"
|
||||||
|
"fn .() -> . {}\n"
|
||||||
|
" ^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:2:11 error: unable to determine function return type\n"
|
||||||
|
"fn .() -> . {}\n"
|
||||||
|
" ^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:3:6 error: expected ')' for function declaration\n"
|
||||||
|
"fn x(.) . {}\n"
|
||||||
|
" ^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:3:9 error: expected '->' for function declaration\n"
|
||||||
|
"fn x(.) . {}\n"
|
||||||
|
" ^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:4:3 error: expected decoration\n"
|
||||||
|
"[[.,.]] fn -> {}\n"
|
||||||
|
" ^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:4:5 error: expected decoration\n"
|
||||||
|
"[[.,.]] fn -> {}\n"
|
||||||
|
" ^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:4:12 error: expected identifier for function declaration\n"
|
||||||
|
"[[.,.]] fn -> {}\n"
|
||||||
|
" ^^\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ParserImplErrorResyncTest, AssignmentStatement) {
|
||||||
|
EXPECT(R"(
|
||||||
|
fn f() -> void {
|
||||||
|
blah blah blah blah;
|
||||||
|
good = 1;
|
||||||
|
blah blah blah blah;
|
||||||
|
x = .;
|
||||||
|
good = 1;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
"test.wgsl:3:8 error: expected '=' for assignment\n"
|
||||||
|
" blah blah blah blah;\n"
|
||||||
|
" ^^^^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:5:8 error: expected '=' for assignment\n"
|
||||||
|
" blah blah blah blah;\n"
|
||||||
|
" ^^^^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:6:7 error: unable to parse right side of assignment\n"
|
||||||
|
" x = .;\n"
|
||||||
|
" ^\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ParserImplErrorResyncTest, DiscardStatement) {
|
||||||
|
EXPECT(R"(
|
||||||
|
fn f() -> void {
|
||||||
|
discard blah blah blah;
|
||||||
|
a = 1;
|
||||||
|
discard blah blah blah;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
"test.wgsl:3:11 error: expected ';' for discard statement\n"
|
||||||
|
" discard blah blah blah;\n"
|
||||||
|
" ^^^^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:5:11 error: expected ';' for discard statement\n"
|
||||||
|
" discard blah blah blah;\n"
|
||||||
|
" ^^^^\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ParserImplErrorResyncTest, StructMembers) {
|
||||||
|
EXPECT(R"(
|
||||||
|
struct S {
|
||||||
|
blah blah blah;
|
||||||
|
a : i32;
|
||||||
|
blah blah blah;
|
||||||
|
b : i32;
|
||||||
|
[[block]] x : i32;
|
||||||
|
c : i32;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
"test.wgsl:3:10 error: expected ':' for struct member\n"
|
||||||
|
" blah blah blah;\n"
|
||||||
|
" ^^^^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:5:10 error: expected ':' for struct member\n"
|
||||||
|
" blah blah blah;\n"
|
||||||
|
" ^^^^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:7:7 error: struct decoration type cannot be used for "
|
||||||
|
"struct member\n"
|
||||||
|
" [[block]] x : i32;\n"
|
||||||
|
" ^^^^^\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the forward scan in resynchronize() stop at nested sync points.
|
||||||
|
// In this test the inner resynchronize() is looking for a terminating ';', and
|
||||||
|
// the outer resynchronize() is looking for a terminating '}' for the function
|
||||||
|
// scope.
|
||||||
|
TEST_F(ParserImplErrorResyncTest, NestedSyncPoints) {
|
||||||
|
EXPECT(R"(
|
||||||
|
fn f() -> void {
|
||||||
|
x = 1;
|
||||||
|
discard
|
||||||
|
}
|
||||||
|
struct S { blah };
|
||||||
|
)",
|
||||||
|
"test.wgsl:5:1 error: expected ';' for discard statement\n"
|
||||||
|
"}\n"
|
||||||
|
"^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:6:17 error: expected ':' for struct member\n"
|
||||||
|
"struct S { blah };\n"
|
||||||
|
" ^\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ParserImplErrorResyncTest, BracketCounting) {
|
||||||
|
EXPECT(R"(
|
||||||
|
[[woof[[[[]]]]]]
|
||||||
|
fn f(x(((())))) -> void {
|
||||||
|
meow = {{{}}}
|
||||||
|
}
|
||||||
|
struct S { blah };
|
||||||
|
)",
|
||||||
|
"test.wgsl:2:3 error: expected decoration\n"
|
||||||
|
"[[woof[[[[]]]]]]\n"
|
||||||
|
" ^^^^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:3:7 error: expected ':' for parameter\n"
|
||||||
|
"fn f(x(((())))) -> void {\n"
|
||||||
|
" ^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:4:10 error: unable to parse right side of assignment\n"
|
||||||
|
" meow = {{{}}}\n"
|
||||||
|
" ^\n"
|
||||||
|
"\n"
|
||||||
|
"test.wgsl:6:17 error: expected ':' for struct member\n"
|
||||||
|
"struct S { blah };\n"
|
||||||
|
" ^\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace wgsl
|
||||||
|
} // namespace reader
|
||||||
|
} // namespace tint
|
|
@ -149,8 +149,8 @@ TEST_F(ParserImplTest, GlobalVariableDecl_InvalidDecoration) {
|
||||||
|
|
||||||
auto e = p->global_variable_decl(decos.value);
|
auto e = p->global_variable_decl(decos.value);
|
||||||
EXPECT_FALSE(e.errored);
|
EXPECT_FALSE(e.errored);
|
||||||
EXPECT_FALSE(e.matched);
|
EXPECT_TRUE(e.matched);
|
||||||
EXPECT_EQ(e.value, nullptr);
|
EXPECT_NE(e.value, nullptr);
|
||||||
|
|
||||||
EXPECT_TRUE(p->has_error());
|
EXPECT_TRUE(p->has_error());
|
||||||
EXPECT_EQ(p->error(),
|
EXPECT_EQ(p->error(),
|
||||||
|
|
|
@ -165,8 +165,8 @@ TEST_F(ParserImplTest, StructDecl_InvalidStructDecorationDecl) {
|
||||||
|
|
||||||
auto s = p->struct_decl(decos.value);
|
auto s = p->struct_decl(decos.value);
|
||||||
EXPECT_FALSE(s.errored);
|
EXPECT_FALSE(s.errored);
|
||||||
EXPECT_FALSE(s.matched);
|
EXPECT_TRUE(s.matched);
|
||||||
EXPECT_EQ(s.value, nullptr);
|
EXPECT_NE(s.value, nullptr);
|
||||||
|
|
||||||
EXPECT_TRUE(p->has_error());
|
EXPECT_TRUE(p->has_error());
|
||||||
EXPECT_EQ(p->error(), "1:9: expected ']]' for decoration list");
|
EXPECT_EQ(p->error(), "1:9: expected ']]' for decoration list");
|
||||||
|
|
|
@ -110,9 +110,10 @@ TEST_F(ParserImplTest, StructMember_InvalidDecoration) {
|
||||||
EXPECT_FALSE(decos.matched);
|
EXPECT_FALSE(decos.matched);
|
||||||
|
|
||||||
auto m = p->expect_struct_member(decos.value);
|
auto m = p->expect_struct_member(decos.value);
|
||||||
|
ASSERT_FALSE(m.errored);
|
||||||
|
ASSERT_NE(m.value, nullptr);
|
||||||
|
|
||||||
ASSERT_TRUE(p->has_error());
|
ASSERT_TRUE(p->has_error());
|
||||||
ASSERT_TRUE(m.errored);
|
|
||||||
ASSERT_EQ(m.value, nullptr);
|
|
||||||
EXPECT_EQ(p->error(),
|
EXPECT_EQ(p->error(),
|
||||||
"1:10: expected signed integer literal for offset decoration");
|
"1:10: expected signed integer literal for offset decoration");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue