wsgl parser: Add expect_block(), use it

`expect_block()` takes a start and end token, along with a function to parse a lexical block body.

This reduces code, keeps error messages consistent, and also gives us a future place to try resynchronising the parser so we can have more than one error emitted.

`expect_paren_block()` and `expect_brace_block()` are convenience helpers for providing the start and end tokens for common block types.

Bug: tint:282
Change-Id: I432a0301727b131a6fce875687b952dfc6889a4b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31736
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 248d78c..ec4fc39 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -435,62 +435,38 @@
   Source source;
   if (match(Token::Type::kLocation, &source)) {
     const char* use = "location decoration";
-
-    if (!expect(use, Token::Type::kParenLeft))
-      return nullptr;
-
-    uint32_t val;
-    if (!expect_positive_sint(use, &val))
-      return nullptr;
-
-    if (!expect(use, Token::Type::kParenRight))
-      return nullptr;
-
-    return std::make_unique<ast::LocationDecoration>(val, source);
+    return expect_paren_block(use, [&] {
+      uint32_t val;
+      bool ok = expect_positive_sint(use, &val);
+      return ok ? std::make_unique<ast::LocationDecoration>(val, source)
+                : nullptr;
+    });
   }
   if (match(Token::Type::kBuiltin, &source)) {
-    if (!expect("builtin decoration", Token::Type::kParenLeft))
-      return nullptr;
-
-    ast::Builtin builtin;
-    std::tie(builtin, source) = expect_builtin();
-    if (builtin == ast::Builtin::kNone)
-      return nullptr;
-
-    if (!expect("builtin decoration", Token::Type::kParenRight))
-      return nullptr;
-
-    return std::make_unique<ast::BuiltinDecoration>(builtin, source);
+    return expect_paren_block("builtin decoration", [&] {
+      ast::Builtin builtin;
+      std::tie(builtin, source) = expect_builtin();
+      return (builtin != ast::Builtin::kNone)
+                 ? std::make_unique<ast::BuiltinDecoration>(builtin, source)
+                 : nullptr;
+    });
   }
   if (match(Token::Type::kBinding, &source)) {
     const char* use = "binding decoration";
-
-    if (!expect(use, Token::Type::kParenLeft))
-      return nullptr;
-
-    uint32_t val;
-    if (!expect_positive_sint(use, &val))
-      return nullptr;
-
-    if (!expect(use, Token::Type::kParenRight))
-      return nullptr;
-
-    return std::make_unique<ast::BindingDecoration>(val, source);
+    return expect_paren_block(use, [&] {
+      uint32_t val;
+      bool ok = expect_positive_sint(use, &val);
+      return ok ? std::make_unique<ast::BindingDecoration>(val, source)
+                : nullptr;
+    });
   }
   if (match(Token::Type::kSet, &source)) {
     const char* use = "set decoration";
-
-    if (!expect(use, Token::Type::kParenLeft))
-      return nullptr;
-
-    uint32_t val;
-    if (!expect_positive_sint(use, &val))
-      return nullptr;
-
-    if (!expect(use, Token::Type::kParenRight))
-      return nullptr;
-
-    return std::make_unique<ast::SetDecoration>(val, source);
+    return expect_paren_block(use, [&] {
+      uint32_t val;
+      bool ok = expect_positive_sint(use, &val);
+      return ok ? std::make_unique<ast::SetDecoration>(val, source) : nullptr;
+    });
   }
 
   return nullptr;
@@ -1200,25 +1176,24 @@
 
   for (;;) {
     Source source;
-    if (!match(Token::Type::kStride, &source)) {
+    if (match(Token::Type::kStride, &source)) {
+      const char* use = "stride decoration";
+      auto deco = expect_paren_block(use, [&] {
+        uint32_t val;
+        bool ok = expect_nonzero_positive_sint(use, &val);
+        return ok ? std::make_unique<ast::StrideDecoration>(val, source)
+                  : nullptr;
+      });
+
+      if (!deco)
+        return false;
+
+      decos.emplace_back(std::move(deco));
+    } else {
       add_error(source, "unknown array decoration");
       return false;
     }
 
-    const char* use = "stride decoration";
-
-    if (!expect(use, Token::Type::kParenLeft))
-      return false;
-
-    uint32_t stride;
-    if (!expect_nonzero_positive_sint(use, &stride))
-      return false;
-
-    decos.push_back(std::make_unique<ast::StrideDecoration>(stride, source));
-
-    if (!expect(use, Token::Type::kParenRight))
-      return false;
-
     if (!match(Token::Type::kComma))
       break;
   }
@@ -1402,43 +1377,23 @@
 // struct_body_decl
 //   : BRACKET_LEFT struct_member* BRACKET_RIGHT
 ast::StructMemberList ParserImpl::struct_body_decl() {
-  auto t = peek();
-  if (!t.IsBraceLeft()) {
-    add_error(t, "missing { for struct declaration");
-    return {};
-  }
-  next();  // Consume the peek
+  return expect_brace_block("struct declaration", [&] {
+    ast::StructMemberList members;
 
-  t = peek();
-  if (t.IsBraceRight()) {
-    next();  // Consume the peek
-    return {};
-  }
+    while (!peek().IsBraceRight() && !peek().IsEof()) {
+      auto mem = struct_member();
+      if (has_error())
+        return ast::StructMemberList{};
+      if (mem == nullptr) {
+        add_error(peek(), "invalid struct member");
+        return ast::StructMemberList{};
+      }
 
-  ast::StructMemberList members;
-  for (;;) {
-    auto mem = struct_member();
-    if (has_error())
-      return {};
-    if (mem == nullptr) {
-      add_error(peek(), "invalid struct member");
-      return {};
+      members.push_back(std::move(mem));
     }
 
-    members.push_back(std::move(mem));
-
-    t = peek();
-    if (t.IsBraceRight() || t.IsEof())
-      break;
-  }
-
-  t = next();
-  if (!t.IsBraceRight()) {
-    add_error(t, "missing } for struct declaration");
-    return {};
-  }
-
-  return members;
+    return members;
+  });
 }
 
 // struct_member
@@ -1519,18 +1474,12 @@
     return nullptr;
 
   const char* use = "offset decoration";
-
-  if (!expect(use, Token::Type::kParenLeft))
-    return nullptr;
-
-  uint32_t val;
-  if (!expect_positive_sint(use, &val))
-    return nullptr;
-
-  if (!expect(use, Token::Type::kParenRight))
-    return nullptr;
-
-  return std::make_unique<ast::StructMemberOffsetDecoration>(val, source);
+  return expect_paren_block(use, [&] {
+    uint32_t val;
+    bool ok = expect_positive_sint(use, &val);
+    return ok ? std::make_unique<ast::StructMemberOffsetDecoration>(val, source)
+              : nullptr;
+  });
 }
 
 // function_decl
@@ -1624,55 +1573,36 @@
 //   | WORKGROUP_SIZE PAREN_LEFT INT_LITERAL
 //         (COMMA INT_LITERAL (COMMA INT_LITERAL)?)? PAREN_RIGHT
 std::unique_ptr<ast::FunctionDecoration> ParserImpl::function_decoration() {
-  auto t = peek();
-  auto source = t.source();
-  if (t.IsWorkgroupSize()) {
-    next();  // Consume the peek
-
-    const char* use = "workgroup_size decoration";
-
-    if (!expect(use, Token::Type::kParenLeft))
-      return nullptr;
-
-    uint32_t x;
-    if (!expect_nonzero_positive_sint("workgroup_size x parameter", &x)) {
-      return nullptr;
-    }
-
-    uint32_t y = 1;
-    uint32_t z = 1;
-    if (match(Token::Type::kComma)) {
-      if (!expect_nonzero_positive_sint("workgroup_size y parameter", &y)) {
-        return nullptr;
+  Source source;
+  if (match(Token::Type::kWorkgroupSize, &source)) {
+    return expect_paren_block("workgroup_size decoration", [&]() {
+      uint32_t x;
+      if (!expect_nonzero_positive_sint("workgroup_size x parameter", &x)) {
+        return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
       }
+      uint32_t y = 1;
+      uint32_t z = 1;
       if (match(Token::Type::kComma)) {
-        if (!expect_nonzero_positive_sint("workgroup_size z parameter", &z)) {
-          return nullptr;
+        if (!expect_nonzero_positive_sint("workgroup_size y parameter", &y)) {
+          return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
+        }
+        if (match(Token::Type::kComma)) {
+          if (!expect_nonzero_positive_sint("workgroup_size z parameter", &z)) {
+            return std::unique_ptr<ast::WorkgroupDecoration>(nullptr);
+          }
         }
       }
-    }
-
-    if (!expect(use, Token::Type::kParenRight))
-      return nullptr;
-
-    return std::make_unique<ast::WorkgroupDecoration>(uint32_t(x), uint32_t(y),
-                                                      uint32_t(z), source);
+      return std::make_unique<ast::WorkgroupDecoration>(x, y, z, source);
+    });
   }
-  if (t.IsStage()) {
-    next();  // Consume the peek
-
-    if (!expect("stage decoration", Token::Type::kParenLeft))
-      return nullptr;
-
-    ast::PipelineStage stage;
-    std::tie(stage, source) = expect_pipeline_stage();
-    if (stage == ast::PipelineStage::kNone)
-      return nullptr;
-
-    if (!expect("stage decoration", Token::Type::kParenRight))
-      return nullptr;
-
-    return std::make_unique<ast::StageDecoration>(stage, source);
+  if (match(Token::Type::kStage, &source)) {
+    return expect_paren_block("stage decoration", [&]() {
+      ast::PipelineStage stage;
+      std::tie(stage, source) = expect_pipeline_stage();
+      return (stage != ast::PipelineStage::kNone)
+                 ? std::make_unique<ast::StageDecoration>(stage, source)
+                 : nullptr;
+    });
   }
   return nullptr;
 }
@@ -1702,16 +1632,11 @@
   if (!expect_ident(use, &name))
     return nullptr;
 
-  if (!expect(use, Token::Type::kParenLeft))
-    return nullptr;
+  auto params = expect_paren_block(use, [&] { return param_list(); });
 
-  auto params = param_list();
   if (has_error())
     return nullptr;
 
-  if (!expect(use, Token::Type::kParenRight))
-    return nullptr;
-
   auto t = next();
   if (!t.IsArrow()) {
     add_error(t, "missing -> for function declaration");
@@ -1808,45 +1733,23 @@
 // body_stmt
 //   : BRACKET_LEFT statements BRACKET_RIGHT
 std::unique_ptr<ast::BlockStatement> ParserImpl::body_stmt() {
-  auto t = peek();
-  if (!t.IsBraceLeft()) {
-    add_error(t, "missing {");
-    return nullptr;
-  }
-
-  next();  // Consume the peek
-
-  auto stmts = statements();
-  if (has_error())
-    return nullptr;
-
-  t = next();
-  if (!t.IsBraceRight()) {
-    add_error(t, "missing }");
-    return nullptr;
-  }
-
-  return stmts;
+  return expect_brace_block("", [&] { return statements(); });
 }
 
 // paren_rhs_stmt
 //   : PAREN_LEFT logical_or_expression PAREN_RIGHT
 std::unique_ptr<ast::Expression> ParserImpl::paren_rhs_stmt() {
-  if (!expect("", Token::Type::kParenLeft))
-    return nullptr;
+  return expect_paren_block("", [&]() -> std::unique_ptr<ast::Expression> {
+    auto expr = logical_or_expression();
+    if (has_error())
+      return nullptr;
 
-  auto expr = logical_or_expression();
-  if (has_error())
-    return nullptr;
-  if (expr == nullptr) {
-    add_error(peek(), "unable to parse expression");
-    return nullptr;
-  }
-
-  if (!expect("", Token::Type::kParenRight))
-    return nullptr;
-
-  return expr;
+    if (expr == nullptr) {
+      add_error(peek(), "unable to parse expression");
+      return nullptr;
+    }
+    return expr;
+  });
 }
 
 // statements
@@ -2174,28 +2077,23 @@
     return nullptr;
   }
 
-  auto t = next();
-  if (!t.IsBraceLeft()) {
-    add_error(t, "missing { for switch statement");
-    return nullptr;
-  }
-
   ast::CaseStatementList body;
-  for (;;) {
-    auto stmt = switch_body();
-    if (has_error())
-      return nullptr;
-    if (stmt == nullptr)
-      break;
+  bool ok = expect_brace_block("switch statement", [&] {
+    for (;;) {
+      auto stmt = switch_body();
+      if (has_error())
+        return false;
+      if (stmt == nullptr)
+        break;
 
-    body.push_back(std::move(stmt));
-  }
+      body.push_back(std::move(stmt));
+    }
+    return true;
+  });
 
-  t = next();
-  if (!t.IsBraceRight()) {
-    add_error(t, "missing } for switch statement");
+  if (!ok)
     return nullptr;
-  }
+
   return std::make_unique<ast::SwitchStatement>(source, std::move(condition),
                                                 std::move(body));
 }
@@ -2224,30 +2122,18 @@
     stmt->set_selectors(std::move(selectors));
   }
 
-  t = next();
-  if (!t.IsColon()) {
-    add_error(t, "missing : for case statement");
-    return nullptr;
-  }
+  const char* use = "case statement";
 
-  t = next();
-  if (!t.IsBraceLeft()) {
-    add_error(t, "missing { for case statement");
+  if (!expect(use, Token::Type::kColon))
     return nullptr;
-  }
 
-  auto body = case_body();
-  if (has_error())
+  auto body = expect_brace_block(use, [&] { return case_body(); });
+
+  if (body == nullptr)
     return nullptr;
 
   stmt->set_body(std::move(body));
 
-  t = next();
-  if (!t.IsBraceRight()) {
-    add_error(t, "missing } for case statement");
-    return nullptr;
-  }
-
   return stmt;
 }
 
@@ -2313,28 +2199,19 @@
   if (!match(Token::Type::kLoop, &source))
     return nullptr;
 
-  auto t = next();
-  if (!t.IsBraceLeft()) {
-    add_error(t, "missing { for loop");
-    return nullptr;
-  }
+  return expect_brace_block(
+      "loop", [&]() -> std::unique_ptr<ast::LoopStatement> {
+        auto body = statements();
+        if (has_error())
+          return nullptr;
 
-  auto body = statements();
-  if (has_error())
-    return nullptr;
+        auto continuing = continuing_stmt();
+        if (has_error())
+          return nullptr;
 
-  auto continuing = continuing_stmt();
-  if (has_error())
-    return nullptr;
-
-  t = next();
-  if (!t.IsBraceRight()) {
-    add_error(t, "missing } for loop");
-    return nullptr;
-  }
-
-  return std::make_unique<ast::LoopStatement>(source, std::move(body),
-                                              std::move(continuing));
+        return std::make_unique<ast::LoopStatement>(source, std::move(body),
+                                                    std::move(continuing));
+      });
 }
 
 ForHeader::ForHeader(std::unique_ptr<ast::Statement> init,
@@ -2408,32 +2285,15 @@
   if (!match(Token::Type::kFor, &source))
     return nullptr;
 
-  if (!expect("for loop", Token::Type::kParenLeft))
+  auto header = expect_paren_block("for loop", [&] { return for_header(); });
+  if (header == nullptr)
     return nullptr;
 
-  auto header = for_header();
-  if (has_error())
-    return nullptr;
+  auto body = expect_brace_block("for loop", [&] { return statements(); });
 
-  if (!expect("for loop", Token::Type::kParenRight))
+  if (body == nullptr)
     return nullptr;
 
-  auto t = next();
-  if (!t.IsBraceLeft()) {
-    add_error(t, "missing for loop {");
-    return nullptr;
-  }
-
-  auto body = statements();
-  if (has_error())
-    return nullptr;
-
-  t = next();
-  if (!t.IsBraceRight()) {
-    add_error(t, "missing for loop }");
-    return nullptr;
-  }
-
   // The for statement is a syntactic sugar on top of the loop statement.
   // We create corresponding nodes in ast with the exact same behaviour
   // as we would expect from the loop statement.
@@ -2609,19 +2469,21 @@
   if (has_error())
     return nullptr;
   if (type != nullptr) {
-    if (!expect("type constructor", Token::Type::kParenLeft))
-      return nullptr;
-
-    t = peek();
     ast::ExpressionList params;
-    if (!t.IsParenRight() && !t.IsEof()) {
-      params = argument_expression_list();
-      if (has_error())
-        return nullptr;
-    }
 
-    if (!expect("type constructor", Token::Type::kParenRight))
+    auto ok = expect_paren_block("type constructor", [&] {
+      t = peek();
+      if (!t.IsParenRight() && !t.IsEof()) {
+        params = argument_expression_list();
+        if (has_error())
+          return false;
+      }
+      return true;
+    });
+
+    if (!ok) {
       return nullptr;
+    }
 
     return std::make_unique<ast::TypeConstructorExpression>(source, type,
                                                             std::move(params));
@@ -3271,36 +3133,30 @@
 
   auto* type = type_decl();
   if (type != nullptr) {
-    if (!expect("type constructor", Token::Type::kParenLeft))
-      return nullptr;
-
     ast::ExpressionList params;
-    auto param = const_expr_internal(depth + 1);
-    if (has_error())
-      return nullptr;
-    if (param == nullptr) {
-      add_error(peek(), "unable to parse constant expression");
-      return nullptr;
-    }
-    params.push_back(std::move(param));
-    for (;;) {
-      t = peek();
-      if (!t.IsComma())
-        break;
-
-      next();  // Consume the peek
-
-      param = const_expr_internal(depth + 1);
+    bool ok = expect_paren_block("type constructor", [&] {
+      auto param = const_expr_internal(depth + 1);
       if (has_error())
-        return nullptr;
+        return false;
       if (param == nullptr) {
         add_error(peek(), "unable to parse constant expression");
-        return nullptr;
+        return false;
       }
       params.push_back(std::move(param));
-    }
+      while (match(Token::Type::kComma)) {
+        param = const_expr_internal(depth + 1);
+        if (has_error())
+          return false;
+        if (param == nullptr) {
+          add_error(peek(), "unable to parse constant expression");
+          return false;
+        }
+        params.push_back(std::move(param));
+      }
+      return true;
+    });
 
-    if (!expect("type constructor", Token::Type::kParenRight))
+    if (!ok)
       return nullptr;
 
     return std::make_unique<ast::TypeConstructorExpression>(source, type,
@@ -3401,6 +3257,36 @@
   return true;
 }
 
+template <typename F, typename T>
+T ParserImpl::expect_block(Token::Type start,
+                           Token::Type end,
+                           const std::string& use,
+                           F&& body) {
+  if (!expect(use, start)) {
+    return {};
+  }
+  auto res = body();
+  if (has_error()) {
+    return {};
+  }
+  if (!expect(use, end)) {
+    return {};
+  }
+  return res;
+}
+
+template <typename F, typename T>
+T ParserImpl::expect_paren_block(const std::string& use, F&& body) {
+  return expect_block(Token::Type::kParenLeft, Token::Type::kParenRight, use,
+                      std::forward<F>(body));
+}
+
+template <typename F, typename T>
+T ParserImpl::expect_brace_block(const std::string& use, F&& body) {
+  return expect_block(Token::Type::kBraceLeft, Token::Type::kBraceRight, use,
+                      std::forward<F>(body));
+}
+
 }  // namespace wgsl
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 91c54fa..e1de67f 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -18,6 +18,7 @@
 #include <deque>
 #include <memory>
 #include <string>
+#include <type_traits>
 #include <unordered_map>
 #include <utility>
 
@@ -435,6 +436,10 @@
   std::unique_ptr<ast::AssignmentStatement> assignment_stmt();
 
  private:
+  /// ResultType resolves to the return type for the function or lambda F.
+  template <typename F>
+  using ResultType = typename std::result_of<F()>::type;
+
   /// @returns true and consumes the next token if it equals |tok|.
   /// @param source if not nullptr, the next token's source is written to this
   /// pointer, regardless of success or error
@@ -476,6 +481,45 @@
   bool expect_ident(const std::string& use,
                     std::string* out,
                     Source* source = nullptr);
+  /// Parses a lexical block starting with the token |start| and ending with
+  /// the token |end|. |body| is called to parse the lexical block body between
+  /// the |start| and |end| tokens.
+  /// If the |start| or |end| tokens are not matched then an error is generated
+  /// and a zero-initialized |T| is returned.
+  /// If |body| raises an error while parsing then a zero-initialized |T| is
+  /// returned.
+  /// @param start the token that begins the lexical block
+  /// @param end the token that ends the lexical block
+  /// @param use a description of what was being parsed if an error was raised
+  /// @param body a function or lambda that is called to parse the lexical block
+  /// body, with the signature T().
+  /// @return the value returned by |body| if no errors are raised, otherwise
+  /// a zero-initialized |T|.
+  template <typename F, typename T = ResultType<F>>
+  T expect_block(Token::Type start,
+                 Token::Type end,
+                 const std::string& use,
+                 F&& body);
+  /// A convenience function that calls |expect_block| passing
+  /// |Token::Type::kParenLeft| and |Token::Type::kParenRight| for the |start|
+  /// and |end| arguments, respectively.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @param body a function or lambda that is called to parse the lexical block
+  /// body, with the signature T().
+  /// @return the value returned by |body| if no errors are raised, otherwise
+  /// a zero-initialized |T|.
+  template <typename F, typename T = ResultType<F>>
+  T expect_paren_block(const std::string& use, F&& body);
+  /// A convenience function that calls |expect_block| passing
+  /// |Token::Type::kBraceLeft| and |Token::Type::kBraceRight| for the |start|
+  /// and |end| arguments, respectively.
+  /// @param use a description of what was being parsed if an error was raised
+  /// @param body a function or lambda that is called to parse the lexical block
+  /// body, with the signature T().
+  /// @return the value returned by |body| if no errors are raised, otherwise
+  /// a zero-initialized |T|.
+  template <typename F, typename T = ResultType<F>>
+  T expect_brace_block(const std::string& use, F&& body);
 
   ast::type::Type* type_decl_pointer(Token t);
   ast::type::Type* type_decl_vector(Token t);
diff --git a/src/reader/wgsl/parser_impl_body_stmt_test.cc b/src/reader/wgsl/parser_impl_body_stmt_test.cc
index 1bd1fbe..e578651 100644
--- a/src/reader/wgsl/parser_impl_body_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_body_stmt_test.cc
@@ -44,14 +44,14 @@
   auto* p = parser("{fn main() -> void {}}");
   auto e = p->body_stmt();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:2: missing }");
+  EXPECT_EQ(p->error(), "1:2: expected '}'");
 }
 
 TEST_F(ParserImplTest, BodyStmt_MissingRightParen) {
   auto* p = parser("{return;");
   auto e = p->body_stmt();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:9: missing }");
+  EXPECT_EQ(p->error(), "1:9: expected '}'");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_else_stmt_test.cc b/src/reader/wgsl/parser_impl_else_stmt_test.cc
index fa5af49..f83fa4a 100644
--- a/src/reader/wgsl/parser_impl_else_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_else_stmt_test.cc
@@ -37,7 +37,7 @@
   auto e = p->else_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:8: missing }");
+  EXPECT_EQ(p->error(), "1:8: expected '}'");
 }
 
 TEST_F(ParserImplTest, ElseStmt_MissingBody) {
@@ -45,7 +45,7 @@
   auto e = p->else_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:5: missing {");
+  EXPECT_EQ(p->error(), "1:5: expected '{'");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_elseif_stmt_test.cc b/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
index 13aff03..93ce334 100644
--- a/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
@@ -55,14 +55,14 @@
   auto* p = parser("elseif (true) { fn main() -> void {}}");
   auto e = p->elseif_stmt();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:17: missing }");
+  EXPECT_EQ(p->error(), "1:17: expected '}'");
 }
 
 TEST_F(ParserImplTest, ElseIfStmt_MissingBody) {
   auto* p = parser("elseif (true)");
   auto e = p->elseif_stmt();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:14: missing {");
+  EXPECT_EQ(p->error(), "1:14: expected '{'");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
index dac8e45..899a6af 100644
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -239,14 +239,14 @@
 
 TEST_F(ParserImplErrorTest, ForLoopMissingLBrace) {
   EXPECT("fn f() -> void { for (var i : i32 = 0; i < 8; i=i+1) } }",
-         "test.wgsl:1:54 error: missing for loop {\n"
+         "test.wgsl:1:54 error: expected '{' for for loop\n"
          "fn f() -> void { for (var i : i32 = 0; i < 8; i=i+1) } }\n"
          "                                                     ^\n");
 }
 
 TEST_F(ParserImplErrorTest, ForLoopMissingRBrace) {
   EXPECT("fn f() -> void { for (var i : i32 = 0; i < 8; i=i+1) {",
-         "test.wgsl:1:55 error: missing for loop }\n"
+         "test.wgsl:1:55 error: expected '}' for for loop\n"
          "fn f() -> void { for (var i : i32 = 0; i < 8; i=i+1) {\n"
          "                                                      ^\n");
 }
@@ -438,14 +438,14 @@
 
 TEST_F(ParserImplErrorTest, FunctionDeclMissingLBrace) {
   EXPECT("fn f() -> void }",
-         "test.wgsl:1:16 error: missing {\n"
+         "test.wgsl:1:16 error: expected '{'\n"
          "fn f() -> void }\n"
          "               ^\n");
 }
 
 TEST_F(ParserImplErrorTest, FunctionDeclMissingRBrace) {
   EXPECT("fn f() -> void {",
-         "test.wgsl:1:17 error: missing }\n"
+         "test.wgsl:1:17 error: expected '}'\n"
          "fn f() -> void {\n"
          "                ^\n");
 }
@@ -647,14 +647,14 @@
 
 TEST_F(ParserImplErrorTest, GlobalDeclStructDeclMissingLBrace) {
   EXPECT("struct S };",
-         "test.wgsl:1:10 error: missing { for struct declaration\n"
+         "test.wgsl:1:10 error: expected '{' for struct declaration\n"
          "struct S };\n"
          "         ^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclStructDeclMissingRBrace) {
   EXPECT("struct S { i : i32;",
-         "test.wgsl:1:20 error: missing } for struct declaration\n"
+         "test.wgsl:1:20 error: expected '}' for struct declaration\n"
          "struct S { i : i32;\n"
          "                   ^\n");
 }
@@ -1101,14 +1101,14 @@
 
 TEST_F(ParserImplErrorTest, LoopMissingLBrace) {
   EXPECT("fn f() -> void { loop } }",
-         "test.wgsl:1:23 error: missing { for loop\n"
+         "test.wgsl:1:23 error: expected '{' for loop\n"
          "fn f() -> void { loop } }\n"
          "                      ^\n");
 }
 
 TEST_F(ParserImplErrorTest, LoopMissingRBrace) {
   EXPECT("fn f() -> void { loop {",
-         "test.wgsl:1:24 error: missing } for loop\n"
+         "test.wgsl:1:24 error: expected '}' for loop\n"
          "fn f() -> void { loop {\n"
          "                       ^\n");
 }
@@ -1157,14 +1157,14 @@
 
 TEST_F(ParserImplErrorTest, SwitchStmtMissingLBrace) {
   EXPECT("fn f() -> void { switch(1) }",
-         "test.wgsl:1:28 error: missing { for switch statement\n"
+         "test.wgsl:1:28 error: expected '{' for switch statement\n"
          "fn f() -> void { switch(1) }\n"
          "                           ^\n");
 }
 
 TEST_F(ParserImplErrorTest, SwitchStmtMissingRBrace) {
   EXPECT("fn f() -> void { switch(1) {",
-         "test.wgsl:1:29 error: missing } for switch statement\n"
+         "test.wgsl:1:29 error: expected '}' for switch statement\n"
          "fn f() -> void { switch(1) {\n"
          "                            ^\n");
 }
@@ -1186,21 +1186,21 @@
 
 TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingColon) {
   EXPECT("fn f() -> void { switch(1) { case 1 {} } }",
-         "test.wgsl:1:37 error: missing : for case statement\n"
+         "test.wgsl:1:37 error: expected ':' for case statement\n"
          "fn f() -> void { switch(1) { case 1 {} } }\n"
          "                                    ^\n");
 }
 
 TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingLBrace) {
   EXPECT("fn f() -> void { switch(1) { case 1: } } }",
-         "test.wgsl:1:38 error: missing { for case statement\n"
+         "test.wgsl:1:38 error: expected '{' for case statement\n"
          "fn f() -> void { switch(1) { case 1: } } }\n"
          "                                     ^\n");
 }
 
 TEST_F(ParserImplErrorTest, SwitchStmtCaseMissingRBrace) {
   EXPECT("fn f() -> void { switch(1) { case 1: {",
-         "test.wgsl:1:39 error: missing } for case statement\n"
+         "test.wgsl:1:39 error: expected '}' for case statement\n"
          "fn f() -> void { switch(1) { case 1: {\n"
          "                                      ^\n");
 }
diff --git a/src/reader/wgsl/parser_impl_for_stmt_test.cc b/src/reader/wgsl/parser_impl_for_stmt_test.cc
index 6fffc65..b599e65 100644
--- a/src/reader/wgsl/parser_impl_for_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -200,7 +200,7 @@
 // Test a for loop with missing left brace is invalid.
 TEST_F(ForStmtErrorTest, MissingLeftBrace) {
   std::string for_str = "for (;;)";
-  std::string error_str = "1:9: missing for loop {";
+  std::string error_str = "1:9: expected '{' for for loop";
 
   TestForWithError(for_str, error_str);
 }
@@ -208,7 +208,7 @@
 // Test a for loop with missing right brace is invalid.
 TEST_F(ForStmtErrorTest, MissingRightBrace) {
   std::string for_str = "for (;;) {";
-  std::string error_str = "1:11: missing for loop }";
+  std::string error_str = "1:11: expected '}' for for loop";
 
   TestForWithError(for_str, error_str);
 }
@@ -275,7 +275,7 @@
 // Test a for loop with a body not matching statements
 TEST_F(ForStmtErrorTest, InvalidBodyMatch) {
   std::string for_str = "for (;;) { fn main() -> void {} }";
-  std::string error_str = "1:12: missing for loop }";
+  std::string error_str = "1:12: expected '}' for for loop";
 
   TestForWithError(for_str, error_str);
 }
diff --git a/src/reader/wgsl/parser_impl_function_decl_test.cc b/src/reader/wgsl/parser_impl_function_decl_test.cc
index 79720a1..549273a 100644
--- a/src/reader/wgsl/parser_impl_function_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decl_test.cc
@@ -174,7 +174,7 @@
   auto f = p->function_decl();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(f, nullptr);
-  EXPECT_EQ(p->error(), "1:19: missing {");
+  EXPECT_EQ(p->error(), "1:19: expected '{'");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_if_stmt_test.cc b/src/reader/wgsl/parser_impl_if_stmt_test.cc
index 63cba71..50c3827 100644
--- a/src/reader/wgsl/parser_impl_if_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_if_stmt_test.cc
@@ -78,7 +78,7 @@
   auto e = p->if_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:10: missing }");
+  EXPECT_EQ(p->error(), "1:10: expected '}'");
 }
 
 TEST_F(ParserImplTest, IfStmt_MissingBody) {
@@ -86,7 +86,7 @@
   auto e = p->if_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:7: missing {");
+  EXPECT_EQ(p->error(), "1:7: expected '{'");
 }
 
 TEST_F(ParserImplTest, IfStmt_InvalidElseif) {
@@ -94,7 +94,7 @@
   auto e = p->if_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:24: missing }");
+  EXPECT_EQ(p->error(), "1:24: expected '}'");
 }
 
 TEST_F(ParserImplTest, IfStmt_InvalidElse) {
@@ -102,7 +102,7 @@
   auto e = p->if_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:18: missing }");
+  EXPECT_EQ(p->error(), "1:18: expected '}'");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_loop_stmt_test.cc b/src/reader/wgsl/parser_impl_loop_stmt_test.cc
index 71962dd..41aea2f 100644
--- a/src/reader/wgsl/parser_impl_loop_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_loop_stmt_test.cc
@@ -70,7 +70,7 @@
   auto e = p->loop_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:6: missing { for loop");
+  EXPECT_EQ(p->error(), "1:6: expected '{' for loop");
 }
 
 TEST_F(ParserImplTest, LoopStmt_MissingBracketRight) {
@@ -78,7 +78,7 @@
   auto e = p->loop_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:17: missing } for loop");
+  EXPECT_EQ(p->error(), "1:17: expected '}' for loop");
 }
 
 TEST_F(ParserImplTest, LoopStmt_InvalidStatements) {
diff --git a/src/reader/wgsl/parser_impl_statement_test.cc b/src/reader/wgsl/parser_impl_statement_test.cc
index ee6df75..2cebedf 100644
--- a/src/reader/wgsl/parser_impl_statement_test.cc
+++ b/src/reader/wgsl/parser_impl_statement_test.cc
@@ -90,7 +90,7 @@
   auto e = p->statement();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:10: missing }");
+  EXPECT_EQ(p->error(), "1:10: expected '}'");
 }
 
 TEST_F(ParserImplTest, Statement_Variable) {
@@ -146,7 +146,7 @@
   auto e = p->statement();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:6: missing { for loop");
+  EXPECT_EQ(p->error(), "1:6: expected '{' for loop");
 }
 
 TEST_F(ParserImplTest, Statement_Assignment) {
@@ -235,7 +235,7 @@
   auto e = p->statement();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:3: missing }");
+  EXPECT_EQ(p->error(), "1:3: expected '}'");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
index 38e8c75..33c4b4f 100644
--- a/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_body_decl_test.cc
@@ -59,7 +59,7 @@
   auto* p = parser("{a : i32;");
   auto m = p->struct_body_decl();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:10: missing } for struct declaration");
+  EXPECT_EQ(p->error(), "1:10: expected '}' for struct declaration");
 }
 
 TEST_F(ParserImplTest, StructBodyDecl_InvalidToken) {
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
index 884fea8..ebf1c4a 100644
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -94,7 +94,7 @@
   auto s = p->struct_decl();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(s, nullptr);
-  EXPECT_EQ(p->error(), "1:10: missing { for struct declaration");
+  EXPECT_EQ(p->error(), "1:10: expected '{' for struct declaration");
 }
 
 TEST_F(ParserImplTest, StructDecl_InvalidStructBody) {
diff --git a/src/reader/wgsl/parser_impl_switch_body_test.cc b/src/reader/wgsl/parser_impl_switch_body_test.cc
index b5d3cd1..6b4078c 100644
--- a/src/reader/wgsl/parser_impl_switch_body_test.cc
+++ b/src/reader/wgsl/parser_impl_switch_body_test.cc
@@ -62,7 +62,7 @@
   auto e = p->switch_body();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:8: missing : for case statement");
+  EXPECT_EQ(p->error(), "1:8: expected ':' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MissingBracketLeft) {
@@ -70,7 +70,7 @@
   auto e = p->switch_body();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:9: missing { for case statement");
+  EXPECT_EQ(p->error(), "1:9: expected '{' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MissingBracketRight) {
@@ -78,7 +78,7 @@
   auto e = p->switch_body();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:18: missing } for case statement");
+  EXPECT_EQ(p->error(), "1:18: expected '}' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_InvalidCaseBody) {
@@ -86,7 +86,7 @@
   auto e = p->switch_body();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:11: missing } for case statement");
+  EXPECT_EQ(p->error(), "1:11: expected '}' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default) {
@@ -105,7 +105,7 @@
   auto e = p->switch_body();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:9: missing : for case statement");
+  EXPECT_EQ(p->error(), "1:9: expected ':' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default_MissingBracketLeft) {
@@ -113,7 +113,7 @@
   auto e = p->switch_body();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:10: missing { for case statement");
+  EXPECT_EQ(p->error(), "1:10: expected '{' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default_MissingBracketRight) {
@@ -121,7 +121,7 @@
   auto e = p->switch_body();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:19: missing } for case statement");
+  EXPECT_EQ(p->error(), "1:19: expected '}' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default_InvalidCaseBody) {
@@ -129,7 +129,7 @@
   auto e = p->switch_body();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:12: missing } for case statement");
+  EXPECT_EQ(p->error(), "1:12: expected '}' for case statement");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_switch_stmt_test.cc b/src/reader/wgsl/parser_impl_switch_stmt_test.cc
index c3f99d6..15b6a5c 100644
--- a/src/reader/wgsl/parser_impl_switch_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_switch_stmt_test.cc
@@ -84,7 +84,7 @@
   auto e = p->switch_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:11: missing { for switch statement");
+  EXPECT_EQ(p->error(), "1:11: expected '{' for switch statement");
 }
 
 TEST_F(ParserImplTest, SwitchStmt_MissingBracketRight) {
@@ -92,7 +92,7 @@
   auto e = p->switch_stmt();
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:12: missing } for switch statement");
+  EXPECT_EQ(p->error(), "1:12: expected '}' for switch statement");
 }
 
 TEST_F(ParserImplTest, SwitchStmt_InvalidBody) {