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:
Ben Clayton 2020-11-11 18:44:36 +00:00 committed by Commit Bot service account
parent ef486bf0e4
commit af8091e353
8 changed files with 552 additions and 158 deletions

View File

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

View File

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

View File

@ -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,75 +252,87 @@ 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;
bool errored = false;
auto decos = decoration_list(); auto decos = decoration_list();
if (decos.errored)
// FUDGE - Abort early if we enter with an error state to avoid accumulating errored = true;
// multiple error messages. if (!synchronized_)
// TODO(ben-clayton) - remove this once resynchronization is implemented.
if (has_error())
return Failure::kErrored; return Failure::kErrored;
auto gv = global_variable_decl(decos.value); auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<bool> {
if (gv.errored) auto gv = global_variable_decl(decos.value);
return Failure::kErrored; if (gv.errored)
if (gv.matched) { return Failure::kErrored;
if (!expect("variable declaration", Token::Type::kSemicolon)) if (gv.matched) {
if (!expect("variable declaration", Token::Type::kSemicolon))
return Failure::kErrored;
module_.AddGlobalVariable(std::move(gv.value));
return true;
}
auto gc = global_constant_decl();
if (gc.errored)
return Failure::kErrored; return Failure::kErrored;
module_.AddGlobalVariable(std::move(gv.value)); if (gc.matched) {
return true; if (!expect("constant declaration", Token::Type::kSemicolon))
} return Failure::kErrored;
auto gc = global_constant_decl(); module_.AddGlobalVariable(std::move(gc.value));
if (gc.errored) { return true;
return Failure::kErrored; }
}
if (gc.matched) { auto ta = type_alias();
if (!expect("constant declaration", Token::Type::kSemicolon)) if (ta.errored)
return Failure::kErrored; return Failure::kErrored;
module_.AddGlobalVariable(std::move(gc.value)); if (ta.matched) {
return true; if (!expect("type alias", Token::Type::kSemicolon))
} return Failure::kErrored;
auto ta = type_alias(); module_.AddConstructedType(ta.value);
if (ta.errored) return true;
return Failure::kErrored; }
if (ta.matched) { auto str = struct_decl(decos.value);
if (!expect("type alias", Token::Type::kSemicolon)) if (str.errored)
return Failure::kErrored; return Failure::kErrored;
module_.AddConstructedType(ta.value); if (str.matched) {
if (!expect("struct declaration", Token::Type::kSemicolon))
return Failure::kErrored;
auto* type = ctx_.type_mgr().Get(std::move(str.value));
register_constructed(type->AsStruct()->name(), type);
module_.AddConstructedType(type);
return true;
}
return Failure::kNoMatch;
});
if (decl.errored)
errored = true;
if (decl.matched)
return true; return true;
}
auto str = struct_decl(decos.value);
if (str.errored)
return Failure::kErrored;
if (str.matched) {
if (!expect("struct declaration", Token::Type::kSemicolon))
return Failure::kErrored;
auto* type = ctx_.type_mgr().Get(std::move(str.value));
register_constructed(type->AsStruct()->name(), type);
module_.AddConstructedType(type);
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,20 +1096,32 @@ 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 decos = decoration_list(); auto member =
if (decos.errored) sync(Token::Type::kSemicolon,
return Failure::kErrored; [&]() -> Expect<std::unique_ptr<ast::StructMember>> {
auto decos = decoration_list();
if (decos.errored)
errored = true;
if (!synchronized_)
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) {
return Failure::kErrored; errored = true;
if (!sync_to(Token::Type::kParenLeft, /* consume: */ false))
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) {
return Failure::kErrored; errored = true;
if (!synchronized_)
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,18 +1322,23 @@ 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) {
ret->append(std::move(stmt.value));
} else {
break; break;
}
ret->append(std::move(stmt.value));
} }
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> {
auto deco = expect_decoration(); bool errored = false;
if (deco.errored)
return Failure::kErrored;
decos.emplace_back(std::move(deco.value)); while (synchronized_) {
auto deco = expect_decoration();
if (deco.errored)
errored = true;
decos.emplace_back(std::move(deco.value));
if (match(Token::Type::kComma)) { if (match(Token::Type::kComma))
continue; continue;
if (is_decoration(peek())) {
// We have two decorations in a bracket without a separating comma.
// e.g. [[location(1) set(2)]]
// ^^^ expected comma
expect(use, Token::Type::kComma);
return Failure::kErrored;
}
break;
} }
if (is_decoration(peek())) { if (errored)
// We have two decorations in a bracket without a separating comma.
// e.g. [[location(1) set(2)]]
// ^^^ expected comma
expect("decoration list", Token::Type::kComma);
return Failure::kErrored; return Failure::kErrored;
}
if (!expect("decoration list", Token::Type::kAttrRight)) 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)) {
std::stringstream err; next();
err << "expected '" << Token::TypeToName(tok) << "'"; synchronized_ = true;
if (!use.empty()) { return true;
err << " for " << use;
}
add_error(t, err.str());
return false;
} }
return true;
std::stringstream err;
err << "expected '" << Token::TypeToName(tok) << "'";
if (!use.empty()) {
err << " for " << use;
}
add_error(t, err.str());
synchronized_ = false;
return false;
} }
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,11 +2932,14 @@ 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>
@ -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;
} }
auto res = body();
if (res.errored) { return sync(end, [&]() -> T {
return Failure::kErrored; auto res = body();
}
if (!expect(use, end)) { if (res.errored)
return Failure::kErrored; return Failure::kErrored;
}
return res; if (!expect(use, end))
return Failure::kErrored;
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

View File

@ -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_;
}; };

View File

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

View File

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

View File

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

View File

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