reader/wgsl: Add support for block comments

Handles nested block comments.

Allow unterminated block comments at EOF, as it is not clear whether
WGSL will allow this or not.

Bug: tint:881
Change-Id: Ieae4e0073dab69f773adb32018a9bdaf4f352116
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/59180
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/reader/wgsl/lexer.cc b/src/reader/wgsl/lexer.cc
index dffed2e..c4eed5f 100644
--- a/src/reader/wgsl/lexer.cc
+++ b/src/reader/wgsl/lexer.cc
@@ -135,13 +135,43 @@
 }
 
 void Lexer::skip_comments() {
-  if (!matches(pos_, "//")) {
+  if (matches(pos_, "//")) {
+    // Line comment: ignore everything until the end of line.
+    while (!is_eof() && !matches(pos_, "\n")) {
+      pos_++;
+      location_.column++;
+    }
     return;
   }
 
-  while (!is_eof() && !matches(pos_, "\n")) {
-    pos_++;
-    location_.column++;
+  if (matches(pos_, "/*")) {
+    // Block comment: ignore everything until the closing '*/' token.
+    pos_ += 2;
+    location_.column += 2;
+
+    int depth = 1;
+    while (!is_eof() && depth > 0) {
+      if (matches(pos_, "/*")) {
+        // Start of block comment: increase nesting depth.
+        pos_ += 2;
+        location_.column += 2;
+        depth++;
+      } else if (matches(pos_, "*/")) {
+        // End of block comment: decrease nesting depth.
+        pos_ += 2;
+        location_.column += 2;
+        depth--;
+      } else if (matches(pos_, "\n")) {
+        // Newline: skip and update source location.
+        pos_++;
+        location_.line++;
+        location_.column = 1;
+      } else {
+        // Anything else: skip and update source location.
+        pos_++;
+        location_.column++;
+      }
+    }
   }
 }
 
diff --git a/src/reader/wgsl/lexer_test.cc b/src/reader/wgsl/lexer_test.cc
index 52a6f1b..b134bc0 100644
--- a/src/reader/wgsl/lexer_test.cc
+++ b/src/reader/wgsl/lexer_test.cc
@@ -48,7 +48,7 @@
   EXPECT_TRUE(t.IsEof());
 }
 
-TEST_F(LexerTest, Skips_Comments) {
+TEST_F(LexerTest, Skips_Comments_Line) {
   Source::FileContent content(R"(//starts with comment
 ident1 //ends with comment
 // blank line
@@ -75,6 +75,41 @@
   EXPECT_TRUE(t.IsEof());
 }
 
+TEST_F(LexerTest, Skips_Comments_Block) {
+  Source::FileContent content(R"(/* comment
+text */ident)");
+  Lexer l("test.wgsl", &content);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 2u);
+  EXPECT_EQ(t.source().range.begin.column, 8u);
+  EXPECT_EQ(t.source().range.end.line, 2u);
+  EXPECT_EQ(t.source().range.end.column, 13u);
+  EXPECT_EQ(t.to_str(), "ident");
+
+  t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+
+TEST_F(LexerTest, Skips_Comments_Block_Nested) {
+  Source::FileContent content(R"(/* comment
+text // nested line comments are ignored /* more text
+/////**/ */*/ident)");
+  Lexer l("test.wgsl", &content);
+
+  auto t = l.next();
+  EXPECT_TRUE(t.IsIdentifier());
+  EXPECT_EQ(t.source().range.begin.line, 3u);
+  EXPECT_EQ(t.source().range.begin.column, 14u);
+  EXPECT_EQ(t.source().range.end.line, 3u);
+  EXPECT_EQ(t.source().range.end.column, 19u);
+  EXPECT_EQ(t.to_str(), "ident");
+
+  t = l.next();
+  EXPECT_TRUE(t.IsEof());
+}
+
 struct FloatData {
   const char* input;
   float result;
diff --git a/src/reader/wgsl/parser_impl_test.cc b/src/reader/wgsl/parser_impl_test.cc
index a7ba36e..588967e 100644
--- a/src/reader/wgsl/parser_impl_test.cc
+++ b/src/reader/wgsl/parser_impl_test.cc
@@ -48,6 +48,26 @@
   EXPECT_EQ(p->error(), "2:15: unable to determine function return type");
 }
 
+TEST_F(ParserImplTest, Comments) {
+  auto p = parser(R"(
+/**
+ * Here is my shader.
+ *
+ * /* I can nest /**/ comments. */
+ * // I can nest line comments too.
+ **/
+[[stage(fragment)]] // This is the stage
+fn main(/*
+no
+parameters
+*/) -> [[location(0)]] vec4<f32> {
+  return/*block_comments_delimit_tokens*/vec4<f32>(.4, .2, .3, 1);
+}/* unterminated block comments are OK at EOF...)");
+
+  ASSERT_TRUE(p->Parse()) << p->error();
+  ASSERT_EQ(1u, p->program().AST().Functions().size());
+}
+
 TEST_F(ParserImplTest, GetRegisteredType) {
   auto p = parser("");
   auto* alias = create<ast::Alias>(Sym("my_alias"), ty.i32());