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_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",
|
||||
|
|
|
@ -479,6 +479,7 @@ if(${TINT_BUILD_WGSL_READER})
|
|||
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
|
||||
|
|
|
@ -83,7 +83,11 @@ using Maybe = ParserImpl::Maybe<T>;
|
|||
/// 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 @@ bool is_decoration(Token t) {
|
|||
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 @@ bool ParserImpl::Parse() {
|
|||
// 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 @@ Expect<bool> ParserImpl::expect_global_decl() {
|
|||
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 @@ Maybe<std::unique_ptr<ast::type::StructType>> ParserImpl::struct_decl(
|
|||
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 @@ Maybe<std::unique_ptr<ast::type::StructType>> ParserImpl::struct_decl(
|
|||
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 @@ Maybe<std::unique_ptr<ast::type::StructType>> ParserImpl::struct_decl(
|
|||
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 @@ Expect<ast::StructMemberList> ParserImpl::expect_struct_body_decl() {
|
|||
// : 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 @@ Expect<std::unique_ptr<ast::StructMember>> ParserImpl::expect_struct_member(
|
|||
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 @@ Maybe<std::unique_ptr<ast::Function>> ParserImpl::function_header() {
|
|||
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 @@ Expect<std::unique_ptr<ast::Expression>> ParserImpl::expect_paren_rhs_stmt() {
|
|||
// 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 @@ Maybe<std::unique_ptr<ast::Statement>> ParserImpl::statement() {
|
|||
// 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 @@ Maybe<std::unique_ptr<ast::ReturnStatement>> ParserImpl::return_stmt() {
|
|||
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 @@ Maybe<std::unique_ptr<ast::SwitchStatement>> ParserImpl::switch_stmt() {
|
|||
|
||||
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 @@ Expect<ast::CaseSelectorList> ParserImpl::expect_case_selectors() {
|
|||
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 @@ ParserImpl::expect_const_expr_internal(uint32_t depth) {
|
|||
}
|
||||
|
||||
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<ast::DecorationList> ParserImpl::decoration_list() {
|
|||
}
|
||||
|
||||
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 @@ Maybe<bool> ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) {
|
|||
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::match(Token::Type tok, Source* source /*= nullptr*/) {
|
|||
}
|
||||
|
||||
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<uint32_t> ParserImpl::expect_nonzero_positive_sint(
|
|||
}
|
||||
|
||||
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 @@ T ParserImpl::expect_block(Token::Type start,
|
|||
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 @@ T ParserImpl::expect_lt_gt_block(const std::string& use, 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 reader
|
||||
} // namespace tint
|
||||
|
|
|
@ -606,31 +606,35 @@ class ParserImpl {
|
|||
/// 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 @@ class ParserImpl {
|
|||
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 @@ class ParserImpl {
|
|||
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_;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
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(),
|
||||
|
|
|
@ -165,8 +165,8 @@ TEST_F(ParserImplTest, StructDecl_InvalidStructDecorationDecl) {
|
|||
|
||||
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");
|
||||
|
|
|
@ -110,9 +110,10 @@ TEST_F(ParserImplTest, StructMember_InvalidDecoration) {
|
|||
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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue