[wgsl-reader] Add `discard` parsing.

This CL adds parsing of the `discard` keyword to the WGSL reader.

Bug: tint:167
Change-Id: Iff91076a65715131f5f0f9bd1c8b6bf3a37cd3c7
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/25604
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/reader/wgsl/lexer.cc b/src/reader/wgsl/lexer.cc
index 5fd1ef4..c1263b7 100644
--- a/src/reader/wgsl/lexer.cc
+++ b/src/reader/wgsl/lexer.cc
@@ -495,6 +495,8 @@
     return {Token::Type::kContinue, source, "continue"};
   if (str == "continuing")
     return {Token::Type::kContinuing, source, "continuing"};
+  if (str == "discard")
+    return {Token::Type::kDiscard, source, "discard"};
   if (str == "default")
     return {Token::Type::kDefault, source, "default"};
   if (str == "else")
diff --git a/src/reader/wgsl/lexer_test.cc b/src/reader/wgsl/lexer_test.cc
index de64e6f..aa731af 100644
--- a/src/reader/wgsl/lexer_test.cc
+++ b/src/reader/wgsl/lexer_test.cc
@@ -425,6 +425,7 @@
                     TokenData{"continue", Token::Type::kContinue},
                     TokenData{"continuing", Token::Type::kContinuing},
                     TokenData{"default", Token::Type::kDefault},
+                    TokenData{"discard", Token::Type::kDiscard},
                     TokenData{"else", Token::Type::kElse},
                     TokenData{"elseif", Token::Type::kElseIf},
                     TokenData{"entry_point", Token::Type::kEntryPoint},
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index c3e7318..dd4a625 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -29,6 +29,7 @@
 #include "src/ast/cast_expression.h"
 #include "src/ast/continue_statement.h"
 #include "src/ast/decorated_variable.h"
+#include "src/ast/discard_statement.h"
 #include "src/ast/else_statement.h"
 #include "src/ast/fallthrough_statement.h"
 #include "src/ast/float_literal.h"
@@ -1426,6 +1427,7 @@
 //   | variable_stmt SEMICOLON
 //   | break_stmt SEMICOLON
 //   | continue_stmt SEMICOLON
+//   | DISCARD SEMICOLON
 //   | KILL SEMICOLON
 //   | assignment_stmt SEMICOLON
 std::unique_ptr<ast::Statement> ParserImpl::statement() {
@@ -1513,6 +1515,18 @@
     return cont;
   }
 
+  if (t.IsDiscard()) {
+    auto source = t.source();
+    next();  // Consume the peek
+
+    t = next();
+    if (!t.IsSemicolon()) {
+      set_error(t, "missing ;");
+      return nullptr;
+    }
+    return std::make_unique<ast::DiscardStatement>(source);
+  }
+
   if (t.IsKill()) {
     auto source = t.source();
     next();  // Consume the peek
diff --git a/src/reader/wgsl/parser_impl_statement_test.cc b/src/reader/wgsl/parser_impl_statement_test.cc
index 9df6b49..f5f06f0 100644
--- a/src/reader/wgsl/parser_impl_statement_test.cc
+++ b/src/reader/wgsl/parser_impl_statement_test.cc
@@ -221,6 +221,22 @@
   EXPECT_EQ(p->error(), "1:5: missing ;");
 }
 
+TEST_F(ParserImplTest, Statement_Discard) {
+  auto* p = parser("discard;");
+  auto e = p->statement();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  EXPECT_NE(e, nullptr);
+  ASSERT_TRUE(e->IsDiscard());
+}
+
+TEST_F(ParserImplTest, Statement_Discard_MissingSemicolon) {
+  auto* p = parser("discard");
+  auto e = p->statement();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(e, nullptr);
+  EXPECT_EQ(p->error(), "1:8: missing ;");
+}
+
 }  // namespace
 }  // namespace wgsl
 }  // namespace reader
diff --git a/src/reader/wgsl/token.cc b/src/reader/wgsl/token.cc
index 0a91ca6..91c489a 100644
--- a/src/reader/wgsl/token.cc
+++ b/src/reader/wgsl/token.cc
@@ -131,6 +131,8 @@
       return "continue";
     case Token::Type::kContinuing:
       return "continuing";
+    case Token::Type::kDiscard:
+      return "discard";
     case Token::Type::kDefault:
       return "default";
     case Token::Type::kElse:
diff --git a/src/reader/wgsl/token.h b/src/reader/wgsl/token.h
index 8545cfa..8e2ffec 100644
--- a/src/reader/wgsl/token.h
+++ b/src/reader/wgsl/token.h
@@ -142,6 +142,8 @@
     kContinue,
     /// A 'continuing'
     kContinuing,
+    /// A 'discard'
+    kDiscard,
     /// A 'default'
     kDefault,
     /// A 'else'
@@ -395,6 +397,8 @@
   bool IsContinue() const { return type_ == Type::kContinue; }
   /// @returns true if token is a 'continuing'
   bool IsContinuing() const { return type_ == Type::kContinuing; }
+  /// @returns true if token is a 'discard'
+  bool IsDiscard() const { return type_ == Type::kDiscard; }
   /// @returns true if token is a 'default'
   bool IsDefault() const { return type_ == Type::kDefault; }
   /// @returns true if token is a 'else'