reader/wgsl: Add support for interpolate attribute

Bug: tint:746
Change-Id: I43486c0244a893a73b734e888fc8811c8e954ab6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56242
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 3c3a1ea..0a0998f 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -110,6 +110,7 @@
 const char kBlockDecoration[] = "block";
 const char kBuiltinDecoration[] = "builtin";
 const char kGroupDecoration[] = "group";
+const char kInterpolateDecoration[] = "interpolate";
 const char kLocationDecoration[] = "location";
 const char kOverrideDecoration[] = "override";
 const char kSizeDecoration[] = "size";
@@ -126,9 +127,9 @@
   auto s = t.to_str();
   return s == kAlignDecoration || s == kBindingDecoration ||
          s == kBlockDecoration || s == kBuiltinDecoration ||
-         s == kGroupDecoration || s == kLocationDecoration ||
-         s == kOverrideDecoration || s == kSetDecoration ||
-         s == kSizeDecoration || s == kStageDecoration ||
+         s == kGroupDecoration || s == kInterpolateDecoration ||
+         s == kLocationDecoration || s == kOverrideDecoration ||
+         s == kSetDecoration || s == kSizeDecoration || s == kStageDecoration ||
          s == kStrideDecoration || s == kWorkgroupSizeDecoration;
 }
 
@@ -2995,6 +2996,41 @@
     });
   }
 
+  if (s == kInterpolateDecoration) {
+    return expect_paren_block("interpolate decoration", [&]() -> Result {
+      ast::InterpolationType type;
+      ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone;
+
+      auto type_tok = next();
+      auto type_str = type_tok.to_str();
+      if (type_str == "perspective") {
+        type = ast::InterpolationType::kPerspective;
+      } else if (type_str == "linear") {
+        type = ast::InterpolationType::kLinear;
+      } else if (type_str == "flat") {
+        type = ast::InterpolationType::kFlat;
+      } else {
+        return add_error(type_tok, "invalid interpolation type");
+      }
+
+      if (match(Token::Type::kComma)) {
+        auto sampling_tok = next();
+        auto sampling_str = sampling_tok.to_str();
+        if (sampling_str == "center") {
+          sampling = ast::InterpolationSampling::kCenter;
+        } else if (sampling_str == "centroid") {
+          sampling = ast::InterpolationSampling::kCentroid;
+        } else if (sampling_str == "sample") {
+          sampling = ast::InterpolationSampling::kSample;
+        } else {
+          return add_error(sampling_tok, "invalid interpolation sampling");
+        }
+      }
+
+      return create<ast::InterpolateDecoration>(t.source(), type, sampling);
+    });
+  }
+
   if (s == kBuiltinDecoration) {
     return expect_paren_block("builtin decoration", [&]() -> Result {
       auto builtin = expect_builtin();
diff --git a/src/reader/wgsl/parser_impl_variable_decoration_test.cc b/src/reader/wgsl/parser_impl_variable_decoration_test.cc
index 33b1d10..5e0696b 100644
--- a/src/reader/wgsl/parser_impl_variable_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_decoration_test.cc
@@ -174,6 +174,130 @@
   EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
 }
 
+TEST_F(ParserImplTest, Decoration_Interpolate_Flat) {
+  auto p = parser("interpolate(flat)");
+  auto deco = p->decoration();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto* var_deco = deco.value->As<ast::Decoration>();
+  ASSERT_NE(var_deco, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_deco->Is<ast::InterpolateDecoration>());
+
+  auto* interp = var_deco->As<ast::InterpolateDecoration>();
+  EXPECT_EQ(interp->type(), ast::InterpolationType::kFlat);
+  EXPECT_EQ(interp->sampling(), ast::InterpolationSampling::kNone);
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_Perspective_Center) {
+  auto p = parser("interpolate(perspective, center)");
+  auto deco = p->decoration();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto* var_deco = deco.value->As<ast::Decoration>();
+  ASSERT_NE(var_deco, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_deco->Is<ast::InterpolateDecoration>());
+
+  auto* interp = var_deco->As<ast::InterpolateDecoration>();
+  EXPECT_EQ(interp->type(), ast::InterpolationType::kPerspective);
+  EXPECT_EQ(interp->sampling(), ast::InterpolationSampling::kCenter);
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_Perspective_Centroid) {
+  auto p = parser("interpolate(perspective, centroid)");
+  auto deco = p->decoration();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto* var_deco = deco.value->As<ast::Decoration>();
+  ASSERT_NE(var_deco, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_deco->Is<ast::InterpolateDecoration>());
+
+  auto* interp = var_deco->As<ast::InterpolateDecoration>();
+  EXPECT_EQ(interp->type(), ast::InterpolationType::kPerspective);
+  EXPECT_EQ(interp->sampling(), ast::InterpolationSampling::kCentroid);
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_Linear_Sample) {
+  auto p = parser("interpolate(linear, sample)");
+  auto deco = p->decoration();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto* var_deco = deco.value->As<ast::Decoration>();
+  ASSERT_NE(var_deco, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(var_deco->Is<ast::InterpolateDecoration>());
+
+  auto* interp = var_deco->As<ast::InterpolateDecoration>();
+  EXPECT_EQ(interp->type(), ast::InterpolationType::kLinear);
+  EXPECT_EQ(interp->sampling(), ast::InterpolationSampling::kSample);
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_MissingLeftParen) {
+  auto p = parser("interpolate flat)");
+  auto deco = p->decoration();
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: expected '(' for interpolate decoration");
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_MissingRightParen) {
+  auto p = parser("interpolate(flat");
+  auto deco = p->decoration();
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:17: expected ')' for interpolate decoration");
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_MissingFirstValue) {
+  auto p = parser("interpolate()");
+  auto deco = p->decoration();
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: invalid interpolation type");
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_InvalidFirstValue) {
+  auto p = parser("interpolate(other_thingy)");
+  auto deco = p->decoration();
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: invalid interpolation type");
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_MissingSecondValue) {
+  auto p = parser("interpolate(perspective,)");
+  auto deco = p->decoration();
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:25: invalid interpolation sampling");
+}
+
+TEST_F(ParserImplTest, Decoration_Interpolate_InvalidSecondValue) {
+  auto p = parser("interpolate(perspective, nope)");
+  auto deco = p->decoration();
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:26: invalid interpolation sampling");
+}
+
 TEST_F(ParserImplTest, Decoration_Binding) {
   auto p = parser("binding(4)");
   auto deco = p->decoration();