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>
diff --git a/BUILD.gn b/BUILD.gn
index d98b686..21e2077 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -943,6 +943,7 @@
"src/reader/wgsl/parser_impl_elseif_stmt_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_resync_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_function_decl_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 65ef68d..93daa84 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -479,6 +479,7 @@
reader/wgsl/parser_impl_elseif_stmt_test.cc
reader/wgsl/parser_impl_equality_expression_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_for_stmt_test.cc
reader/wgsl/parser_impl_function_decl_test.cc
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 036a35f..537c05c 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -83,7 +83,11 @@
/// 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
/// 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) {
if (str == "position") {
@@ -122,6 +126,38 @@
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
ParserImpl::ParserImpl(Context* ctx, Source::File const* file)
@@ -198,10 +234,8 @@
// translation_unit
// : global_decl* EOF
void ParserImpl::translation_unit() {
- while (!peek().IsEof()) {
- auto decl = expect_global_decl();
- if (decl.errored)
- break;
+ while (!peek().IsEof() && synchronized_) {
+ expect_global_decl();
}
assert(module_.IsValid());
@@ -218,75 +252,87 @@
if (match(Token::Type::kSemicolon) || match(Token::Type::kEOF))
return true;
+ bool errored = false;
+
auto decos = decoration_list();
-
- // FUDGE - Abort early if we enter with an error state to avoid accumulating
- // multiple error messages.
- // TODO(ben-clayton) - remove this once resynchronization is implemented.
- if (has_error())
+ if (decos.errored)
+ errored = true;
+ if (!synchronized_)
return Failure::kErrored;
- auto gv = global_variable_decl(decos.value);
- if (gv.errored)
- return Failure::kErrored;
- if (gv.matched) {
- if (!expect("variable declaration", Token::Type::kSemicolon))
+ auto decl = sync(Token::Type::kSemicolon, [&]() -> Maybe<bool> {
+ auto gv = global_variable_decl(decos.value);
+ if (gv.errored)
+ return Failure::kErrored;
+ 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;
- module_.AddGlobalVariable(std::move(gv.value));
- return true;
- }
+ if (gc.matched) {
+ if (!expect("constant declaration", Token::Type::kSemicolon))
+ return Failure::kErrored;
- auto gc = global_constant_decl();
- if (gc.errored) {
- return Failure::kErrored;
- }
- if (gc.matched) {
- if (!expect("constant declaration", Token::Type::kSemicolon))
+ module_.AddGlobalVariable(std::move(gc.value));
+ return true;
+ }
+
+ auto ta = type_alias();
+ if (ta.errored)
return Failure::kErrored;
- module_.AddGlobalVariable(std::move(gc.value));
- return true;
- }
+ if (ta.matched) {
+ if (!expect("type alias", Token::Type::kSemicolon))
+ return Failure::kErrored;
- auto ta = type_alias();
- if (ta.errored)
- return Failure::kErrored;
+ module_.AddConstructedType(ta.value);
+ return true;
+ }
- if (ta.matched) {
- if (!expect("type alias", Token::Type::kSemicolon))
+ auto str = struct_decl(decos.value);
+ if (str.errored)
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;
- }
-
- 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);
if (func.errored)
- return Failure::kErrored;
+ errored = true;
if (func.matched) {
module_.AddFunction(std::move(func.value));
return true;
}
+ if (errored)
+ return Failure::kErrored;
+
if (decos.value.size() > 0) {
- add_error(peek(), "expected declaration after decorations");
+ add_error(next(), "expected declaration after decorations");
} else {
- add_error(peek(), "unexpected token");
+ add_error(next(), "unexpected token");
}
return Failure::kErrored;
}
@@ -1027,10 +1073,6 @@
if (!match(Token::Type::kStruct))
return Failure::kNoMatch;
- auto struct_decos = cast_decorations<ast::StructDecoration>(decos);
- if (struct_decos.errored)
- return Failure::kErrored;
-
auto name = expect_ident("struct declaration");
if (name.errored)
return Failure::kErrored;
@@ -1039,6 +1081,10 @@
if (body.errored)
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>(
name.value,
std::make_unique<ast::Struct>(source, std::move(struct_decos.value),
@@ -1050,20 +1096,32 @@
Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() {
return expect_brace_block(
"struct declaration", [&]() -> Expect<ast::StructMemberList> {
+ bool errored = false;
+
ast::StructMemberList members;
- while (!peek().IsBraceRight() && !peek().IsEof()) {
- auto decos = decoration_list();
- if (decos.errored)
- return Failure::kErrored;
+ while (synchronized_ && !peek().IsBraceRight() && !peek().IsEof()) {
+ auto member =
+ sync(Token::Type::kSemicolon,
+ [&]() -> 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 (mem.errored)
- return Failure::kErrored;
-
- members.push_back(std::move(mem.value));
+ if (member.errored) {
+ errored = true;
+ } else {
+ members.push_back(std::move(member.value));
+ }
}
+ if (errored)
+ return Failure::kErrored;
+
return members;
});
}
@@ -1072,20 +1130,6 @@
// : struct_member_decoration_decl+ variable_ident_decl SEMICOLON
Expect<std::unique_ptr<ast::StructMember>> ParserImpl::expect_struct_member(
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");
if (decl.errored)
return Failure::kErrored;
@@ -1106,19 +1150,34 @@
Maybe<std::unique_ptr<ast::Function>> ParserImpl::function_decl(
ast::DecorationList& decos) {
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;
+ }
if (!f.matched)
return Failure::kNoMatch;
+ bool errored = false;
+
auto func_decos = cast_decorations<ast::FunctionDecoration>(decos);
if (func_decos.errored)
- return Failure::kErrored;
+ errored = true;
f->set_decorations(std::move(func_decos.value));
auto body = expect_body_stmt();
if (body.errored)
+ errored = true;
+
+ if (errored)
return Failure::kErrored;
f->set_body(std::move(body.value));
@@ -1143,23 +1202,34 @@
return Failure::kNoMatch;
const char* use = "function declaration";
+ bool errored = false;
auto name = expect_ident(use);
- if (name.errored)
- return Failure::kErrored;
+ if (name.errored) {
+ errored = true;
+ if (!sync_to(Token::Type::kParenLeft, /* consume: */ false))
+ return Failure::kErrored;
+ }
auto params = expect_paren_block(use, [&] { return expect_param_list(); });
- if (params.errored)
- return Failure::kErrored;
+ if (params.errored) {
+ errored = true;
+ if (!synchronized_)
+ return Failure::kErrored;
+ }
if (!expect(use, Token::Type::kArrow))
return Failure::kErrored;
auto type = function_type_decl();
- if (type.errored)
- return Failure::kErrored;
- if (!type.matched)
+ if (type.errored) {
+ errored = true;
+ } else if (!type.matched) {
return add_error(peek(), "unable to determine function return type");
+ }
+
+ if (errored)
+ return Failure::kErrored;
return std::make_unique<ast::Function>(source, name.value,
std::move(params.value), type.value);
@@ -1252,18 +1322,23 @@
// statements
// : statement*
Expect<std::unique_ptr<ast::BlockStatement>> ParserImpl::expect_statements() {
+ bool errored = false;
auto ret = std::make_unique<ast::BlockStatement>();
- for (;;) {
+ while (synchronized_) {
auto stmt = statement();
- if (stmt.errored)
- return Failure::kErrored;
- if (!stmt.matched)
+ if (stmt.errored) {
+ errored = true;
+ } else if (stmt.matched) {
+ ret->append(std::move(stmt.value));
+ } else {
break;
-
- ret->append(std::move(stmt.value));
+ }
}
+ if (errored)
+ return Failure::kErrored;
+
return ret;
}
@@ -1287,9 +1362,10 @@
// Skip empty statements
}
- // Non-block statments all end in a semi-colon.
- // TODO(bclayton): We can use this property to synchronize on error.
- auto stmt = non_block_statement();
+ // Non-block statments that error can resynchronize on semicolon.
+ auto stmt =
+ sync(Token::Type::kSemicolon, [&] { return non_block_statement(); });
+
if (stmt.errored)
return Failure::kErrored;
if (stmt.matched)
@@ -1401,6 +1477,7 @@
auto expr = logical_or_expression();
if (expr.errored)
return Failure::kErrored;
+
// TODO(bclayton): Check matched?
return std::make_unique<ast::ReturnStatement>(source, std::move(expr.value));
}
@@ -1540,16 +1617,20 @@
auto body = expect_brace_block("switch statement",
[&]() -> Expect<ast::CaseStatementList> {
+ bool errored = false;
ast::CaseStatementList list;
- for (;;) {
+ while (synchronized_) {
auto stmt = switch_body();
- if (stmt.errored)
- return Failure::kErrored;
+ if (stmt.errored) {
+ errored = true;
+ continue;
+ }
if (!stmt.matched)
break;
-
list.push_back(std::move(stmt.value));
}
+ if (errored)
+ return Failure::kErrored;
return list;
});
@@ -1630,11 +1711,8 @@
Maybe<std::unique_ptr<ast::BlockStatement>> ParserImpl::case_body() {
auto ret = std::make_unique<ast::BlockStatement>();
for (;;) {
- auto t = peek();
- if (t.IsFallthrough()) {
- auto source = t.source();
- next(); // Consume the peek
-
+ Source source;
+ if (match(Token::Type::kFallthrough, &source)) {
if (!expect("fallthrough statement", Token::Type::kSemicolon))
return Failure::kErrored;
@@ -2571,19 +2649,23 @@
}
Maybe<ast::DecorationList> ParserImpl::decoration_list() {
+ bool errored = false;
bool matched = false;
ast::DecorationList decos;
- while (true) {
+ while (synchronized_) {
auto list = decoration_bracketed_list(decos);
if (list.errored)
- return Failure::kErrored;
+ errored = true;
if (!list.matched)
break;
matched = true;
}
+ if (errored)
+ return Failure::kErrored;
+
if (!matched)
return Failure::kNoMatch;
@@ -2591,6 +2673,8 @@
}
Maybe<bool> ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) {
+ const char* use = "decoration list";
+
if (!match(Token::Type::kAttrLeft)) {
return Failure::kNoMatch;
}
@@ -2599,30 +2683,37 @@
if (match(Token::Type::kAttrRight, &source))
return add_error(source, "empty decoration list");
- while (true) {
- auto deco = expect_decoration();
- if (deco.errored)
- return Failure::kErrored;
+ return sync(Token::Type::kAttrRight, [&]() -> Expect<bool> {
+ bool errored = false;
- 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)) {
- continue;
+ if (match(Token::Type::kComma))
+ 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())) {
- // 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);
+ if (errored)
return Failure::kErrored;
- }
- if (!expect("decoration list", Token::Type::kAttrRight))
+ if (!expect(use, Token::Type::kAttrRight))
return Failure::kErrored;
return true;
- }
+ });
}
Expect<std::unique_ptr<ast::Decoration>> ParserImpl::expect_decoration() {
@@ -2791,25 +2882,29 @@
}
bool ParserImpl::expect(const std::string& use, Token::Type tok) {
- auto t = next();
- if (!t.Is(tok)) {
- std::stringstream err;
- err << "expected '" << Token::TypeToName(tok) << "'";
- if (!use.empty()) {
- err << " for " << use;
- }
- add_error(t, err.str());
- return false;
+ auto t = peek();
+ if (t.Is(tok)) {
+ next();
+ synchronized_ = true;
+ return true;
}
- 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) {
- auto t = next();
-
+ auto t = peek();
if (!t.IsSintLiteral())
return add_error(t.source(), "expected signed integer literal", use);
+ next();
return {t.to_i32(), t.source()};
}
@@ -2837,11 +2932,14 @@
}
Expect<std::string> ParserImpl::expect_ident(const std::string& use) {
- auto t = next();
- if (!t.IsIdentifier())
- return add_error(t.source(), "expected identifier", use);
-
- return {t.to_str(), t.source()};
+ auto t = peek();
+ if (t.IsIdentifier()) {
+ synchronized_ = true;
+ next();
+ return {t.to_str(), t.source()};
+ }
+ synchronized_ = false;
+ return add_error(t.source(), "expected identifier", use);
}
template <typename F, typename T>
@@ -2852,14 +2950,18 @@
if (!expect(use, start)) {
return Failure::kErrored;
}
- auto res = body();
- if (res.errored) {
- return Failure::kErrored;
- }
- if (!expect(use, end)) {
- return Failure::kErrored;
- }
- return res;
+
+ return sync(end, [&]() -> T {
+ auto res = body();
+
+ if (res.errored)
+ return Failure::kErrored;
+
+ if (!expect(use, end))
+ return Failure::kErrored;
+
+ return res;
+ });
}
template <typename F, typename T>
@@ -2880,6 +2982,64 @@
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 reader
} // namespace tint
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 0f5b152..0e7dd45 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -606,31 +606,35 @@
/// pointer, regardless of success or error
bool match(Token::Type tok, Source* source = nullptr);
/// 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 tok the token to test against
/// @returns true if the next token equals |tok|.
bool expect(const std::string& use, Token::Type tok);
/// Parses a signed integer from the next token in the stream, erroring if the
/// 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
/// @returns the parsed integer.
Expect<int32_t> expect_sint(const std::string& use);
/// Parses a signed integer from the next token in the stream, erroring if
/// 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
/// @returns the parsed integer.
Expect<uint32_t> expect_positive_sint(const std::string& use);
/// 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.
- /// 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
/// @returns the parsed integer.
Expect<uint32_t> expect_nonzero_positive_sint(const std::string& use);
/// 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
/// @returns the parsed identifier.
Expect<std::string> expect_ident(const std::string& use);
@@ -684,6 +688,45 @@
template <typename F, typename T = ReturnType<F>>
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
/// error if any of the decorations aren't of the type |T|.
template <typename T>
@@ -712,6 +755,8 @@
diag::List diags_;
std::unique_ptr<Lexer> lexer_;
std::deque<Token> token_queue_;
+ bool synchronized_ = true;
+ std::vector<Token::Type> sync_tokens_;
std::unordered_map<std::string, ast::type::Type*> registered_constructs_;
ast::Module module_;
};
diff --git a/src/reader/wgsl/parser_impl_error_resync_test.cc b/src/reader/wgsl/parser_impl_error_resync_test.cc
new file mode 100644
index 0000000..3427a59
--- /dev/null
+++ b/src/reader/wgsl/parser_impl_error_resync_test.cc
@@ -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
diff --git a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
index 446d93f..d714e96 100644
--- a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -149,8 +149,8 @@
auto e = p->global_variable_decl(decos.value);
EXPECT_FALSE(e.errored);
- EXPECT_FALSE(e.matched);
- EXPECT_EQ(e.value, nullptr);
+ EXPECT_TRUE(e.matched);
+ EXPECT_NE(e.value, nullptr);
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(),
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
index 37c5f06..59a91eb 100644
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -165,8 +165,8 @@
auto s = p->struct_decl(decos.value);
EXPECT_FALSE(s.errored);
- EXPECT_FALSE(s.matched);
- EXPECT_EQ(s.value, nullptr);
+ EXPECT_TRUE(s.matched);
+ EXPECT_NE(s.value, nullptr);
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:9: expected ']]' for decoration list");
diff --git a/src/reader/wgsl/parser_impl_struct_member_test.cc b/src/reader/wgsl/parser_impl_struct_member_test.cc
index d8c3fe7..2ae818a 100644
--- a/src/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_test.cc
@@ -110,9 +110,10 @@
EXPECT_FALSE(decos.matched);
auto m = p->expect_struct_member(decos.value);
+ ASSERT_FALSE(m.errored);
+ ASSERT_NE(m.value, nullptr);
+
ASSERT_TRUE(p->has_error());
- ASSERT_TRUE(m.errored);
- ASSERT_EQ(m.value, nullptr);
EXPECT_EQ(p->error(),
"1:10: expected signed integer literal for offset decoration");
}