tint/ast: Generate ast::StorageClass from intrinsics.def

Emit unit tests for parsing and printing.
Emit benchmarks for parsing.
Uses intrinsics.def as a single-source-of-truth.
The generators provide a way to optimize the enum parsers.

Change-Id: I1669c123d375f24aca45f3ea4abf04d7892673c7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97150
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 3290694..c426520 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1043,6 +1043,7 @@
       "ast/sampled_texture_test.cc",
       "ast/sampler_test.cc",
       "ast/stage_attribute_test.cc",
+      "ast/storage_class_test.cc",
       "ast/storage_texture_test.cc",
       "ast/stride_attribute_test.cc",
       "ast/struct_member_align_attribute_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 8f8f19c..3a80404 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -739,6 +739,7 @@
     ast/sampled_texture_test.cc
     ast/sampler_test.cc
     ast/stage_attribute_test.cc
+    ast/storage_class_test.cc
     ast/storage_texture_test.cc
     ast/stride_attribute_test.cc
     ast/struct_member_align_attribute_test.cc
@@ -1277,6 +1278,7 @@
 
   set(TINT_BENCHMARK_SRC
     "castable_bench.cc"
+    "ast/storage_class_bench.cc"
     "bench/benchmark.cc"
     "reader/wgsl/parser_bench.cc"
   )
diff --git a/src/tint/ast/storage_class.cc b/src/tint/ast/storage_class.cc
index 903d27a..303c04e 100644
--- a/src/tint/ast/storage_class.cc
+++ b/src/tint/ast/storage_class.cc
@@ -12,6 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/storage_class.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
 #include "src/tint/ast/storage_class.h"
 
 namespace tint::ast {
diff --git a/src/tint/ast/storage_class.cc.tmpl b/src/tint/ast/storage_class.cc.tmpl
new file mode 100644
index 0000000..e2903b7
--- /dev/null
+++ b/src/tint/ast/storage_class.cc.tmpl
@@ -0,0 +1,25 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate storage_class.cc
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "storage_class") -}}
+
+#include "src/tint/ast/storage_class.h"
+
+namespace tint::ast {
+
+{{ Eval "ParseEnum" $enum}}
+
+{{ Eval "EnumOStream" $enum}}
+
+}  // namespace tint::ast
diff --git a/src/tint/ast/storage_class.h b/src/tint/ast/storage_class.h
index 7451587..cb21115 100644
--- a/src/tint/ast/storage_class.h
+++ b/src/tint/ast/storage_class.h
@@ -12,6 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/storage_class.h.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
 #ifndef SRC_TINT_AST_STORAGE_CLASS_H_
 #define SRC_TINT_AST_STORAGE_CLASS_H_
 
@@ -22,21 +30,21 @@
 /// Storage class of a given pointer.
 enum class StorageClass {
     kInvalid,
-    kNone,
+    kNone,  // Tint-internal enum entry - not parsed
     kFunction,
     kPrivate,
     kWorkgroup,
     kUniform,
     kStorage,
-    kHandle,
-    kIn,
-    kOut,
+    kHandle,  // Tint-internal enum entry - not parsed
+    kIn,  // Tint-internal enum entry - not parsed
+    kOut,  // Tint-internal enum entry - not parsed
 };
 
 /// @param out the std::ostream to write to
-/// @param sc the StorageClass
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, StorageClass sc);
+/// @param value the StorageClass
+/// @returns `out` so calls can be chained
+std::ostream& operator<<(std::ostream& out, StorageClass value);
 
 /// ParseStorageClass parses a StorageClass from a string.
 /// @param str the string to parse
diff --git a/src/tint/ast/storage_class.h.tmpl b/src/tint/ast/storage_class.h.tmpl
new file mode 100644
index 0000000..d885c72
--- /dev/null
+++ b/src/tint/ast/storage_class.h.tmpl
@@ -0,0 +1,36 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate storage_class.h
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "storage_class") -}}
+
+#ifndef SRC_TINT_AST_STORAGE_CLASS_H_
+#define SRC_TINT_AST_STORAGE_CLASS_H_
+
+#include <ostream>
+
+namespace tint::ast {
+
+/// Storage class of a given pointer.
+{{ Eval "DeclareEnum" $enum}}
+
+/// @returns true if the StorageClass is host-shareable
+/// @param sc the StorageClass
+/// @see https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable
+inline bool IsHostShareable(StorageClass sc) {
+    return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage;
+}
+
+}  // namespace tint::ast
+
+#endif  // SRC_TINT_AST_STORAGE_CLASS_H_
diff --git a/src/tint/ast/storage_class_bench.cc b/src/tint/ast/storage_class_bench.cc
new file mode 100644
index 0000000..d1232df
--- /dev/null
+++ b/src/tint/ast/storage_class_bench.cc
@@ -0,0 +1,81 @@
+// Copyright 2022 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/storage_class_bench.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/ast/storage_class.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::ast {
+namespace {
+
+void StorageClassParser(::benchmark::State& state) {
+    std::array kStrings{
+        "fccnctin",
+        "ucti3",
+        "functVon",
+        "function",
+        "1unction",
+        "unJtqqon",
+        "llun77tion",
+        "ppqqivtHH",
+        "prcv",
+        "bivaGe",
+        "private",
+        "priviive",
+        "8WWivate",
+        "pxxvate",
+        "wXkgrggup",
+        "worXVup",
+        "3orkgroup",
+        "workgroup",
+        "workgroEp",
+        "woTTPkroup",
+        "ddorkroxxp",
+        "u44iform",
+        "unSSfoVVm",
+        "RniR22m",
+        "uniform",
+        "uFfo9m",
+        "uniorm",
+        "VOORRHrm",
+        "straye",
+        "llntrrr77ge",
+        "stor4g00",
+        "storage",
+        "trooe",
+        "zzrage",
+        "siioppa1",
+    };
+    for (auto _ : state) {
+        for (auto& str : kStrings) {
+            auto result = ParseStorageClass(str);
+            benchmark::DoNotOptimize(result);
+        }
+    }
+}
+
+BENCHMARK(StorageClassParser);
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/storage_class_bench.cc.tmpl b/src/tint/ast/storage_class_bench.cc.tmpl
new file mode 100644
index 0000000..d9ea8cb
--- /dev/null
+++ b/src/tint/ast/storage_class_bench.cc.tmpl
@@ -0,0 +1,29 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate storage_class_bench.cc
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "storage_class") -}}
+
+#include "src/tint/ast/storage_class.h"
+
+#include <array>
+
+#include "benchmark/benchmark.h"
+
+namespace tint::ast {
+namespace {
+
+{{ Eval "BenchmarkParseEnum" $enum }}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/storage_class_test.cc b/src/tint/ast/storage_class_test.cc
new file mode 100644
index 0000000..2085168
--- /dev/null
+++ b/src/tint/ast/storage_class_test.cc
@@ -0,0 +1,94 @@
+// Copyright 2022 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by tools/src/cmd/gen
+// using the template:
+//   src/tint/ast/storage_class_test.cc.tmpl
+//
+// Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/ast/storage_class.h"
+
+#include <string>
+
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::ast {
+namespace {
+
+namespace parse_print_tests {
+
+struct Case {
+    const char* string;
+    StorageClass value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+    return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+    {"function", StorageClass::kFunction},
+    {"private", StorageClass::kPrivate},
+    {"workgroup", StorageClass::kWorkgroup},
+    {"uniform", StorageClass::kUniform},
+    {"storage", StorageClass::kStorage},
+};
+
+static constexpr Case kInvalidCases[] = {
+    {"fccnctin", StorageClass::kInvalid},
+    {"ucti3", StorageClass::kInvalid},
+    {"functVon", StorageClass::kInvalid},
+    {"priv1te", StorageClass::kInvalid},
+    {"pqiJate", StorageClass::kInvalid},
+    {"privat7ll", StorageClass::kInvalid},
+    {"workroppqHH", StorageClass::kInvalid},
+    {"workru", StorageClass::kInvalid},
+    {"wbkgGoup", StorageClass::kInvalid},
+    {"unifiivm", StorageClass::kInvalid},
+    {"8WWiform", StorageClass::kInvalid},
+    {"uxxform", StorageClass::kInvalid},
+    {"sXraggg", StorageClass::kInvalid},
+    {"traXe", StorageClass::kInvalid},
+    {"stor3ge", StorageClass::kInvalid},
+};
+
+using StorageClassParseTest = testing::TestWithParam<Case>;
+
+TEST_P(StorageClassParseTest, Parse) {
+    const char* string = GetParam().string;
+    StorageClass expect = GetParam().value;
+    EXPECT_EQ(expect, ParseStorageClass(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, StorageClassParseTest, testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases, StorageClassParseTest, testing::ValuesIn(kInvalidCases));
+
+using StorageClassPrintTest = testing::TestWithParam<Case>;
+
+TEST_P(StorageClassPrintTest, Print) {
+    StorageClass value = GetParam().value;
+    const char* expect = GetParam().string;
+    EXPECT_EQ(expect, utils::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, StorageClassPrintTest, testing::ValuesIn(kValidCases));
+
+}  // namespace parse_print_tests
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/ast/storage_class_test.cc.tmpl b/src/tint/ast/storage_class_test.cc.tmpl
new file mode 100644
index 0000000..3696aab
--- /dev/null
+++ b/src/tint/ast/storage_class_test.cc.tmpl
@@ -0,0 +1,30 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate storage_class_test.cc
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Import "src/tint/templates/enums.tmpl.inc" -}}
+{{- $enum := (Sem.Enum "storage_class") -}}
+
+#include "src/tint/ast/storage_class.h"
+
+#include <string>
+
+#include "src/tint/ast/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::ast {
+namespace {
+
+{{ Eval "TestParsePrintEnum" $enum}}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/castable_bench.cc b/src/tint/castable_bench.cc
index 7c7e0ef..c9d0c43 100644
--- a/src/tint/castable_bench.cc
+++ b/src/tint/castable_bench.cc
@@ -12,7 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "bench/benchmark.h"
+#include <memory>
+
+#include "benchmark/benchmark.h"
+
+#include "src/tint/castable.h"
 
 namespace tint {
 namespace {
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index 11fb871..30bf8a8 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -25,12 +25,15 @@
 
 // https://gpuweb.github.io/gpuweb/wgsl/#storage-class
 enum storage_class {
+  @internal none
   function
   private
   workgroup
   uniform
   storage
   @internal handle
+  @internal in
+  @internal out
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl/#memory-access-mode
diff --git a/src/tint/reader/wgsl/parser_impl_storage_class_test.cc b/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
index d83bbf4..c40752a 100644
--- a/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_storage_class_test.cc
@@ -26,9 +26,9 @@
     return out;
 }
 
-class StorageClassTest : public ParserImplTestWithParam<StorageClassData> {};
+class ParserStorageClassTest : public ParserImplTestWithParam<StorageClassData> {};
 
-TEST_P(StorageClassTest, Parses) {
+TEST_P(ParserStorageClassTest, Parses) {
     auto params = GetParam();
     auto p = parser(params.input);
 
@@ -42,7 +42,7 @@
 }
 INSTANTIATE_TEST_SUITE_P(
     ParserImplTest,
-    StorageClassTest,
+    ParserStorageClassTest,
     testing::Values(StorageClassData{"uniform", ast::StorageClass::kUniform},
                     StorageClassData{"workgroup", ast::StorageClass::kWorkgroup},
                     StorageClassData{"storage", ast::StorageClass::kStorage},
diff --git a/src/tint/resolver/ctor_conv_intrinsic.cc.tmpl b/src/tint/resolver/ctor_conv_intrinsic.cc.tmpl
index 1f3d7cd..00ac21b 100644
--- a/src/tint/resolver/ctor_conv_intrinsic.cc.tmpl
+++ b/src/tint/resolver/ctor_conv_intrinsic.cc.tmpl
@@ -2,6 +2,9 @@
 --------------------------------------------------------------------------------
 Template file for use with tools/src/cmd/gen to generate ctor_conv_intrinsic.cc
 
+To update the generated file, run:
+    ./tools/run gen
+
 See:
 * tools/src/cmd/gen for structures used by this template
 * https://golang.org/pkg/text/template/ for documentation on the template syntax
diff --git a/src/tint/resolver/ctor_conv_intrinsic.h.tmpl b/src/tint/resolver/ctor_conv_intrinsic.h.tmpl
index a28eba4..349f939 100644
--- a/src/tint/resolver/ctor_conv_intrinsic.h.tmpl
+++ b/src/tint/resolver/ctor_conv_intrinsic.h.tmpl
@@ -2,6 +2,9 @@
 --------------------------------------------------------------------------------
 Template file for use with tools/src/cmd/gen to generate ctor_conv_intrinsic.h
 
+To update the generated file, run:
+    ./tools/run gen
+
 See:
 * tools/src/cmd/gen for structures used by this template
 * https://golang.org/pkg/text/template/ for documentation on the template syntax
diff --git a/src/tint/resolver/intrinsic_table.inl b/src/tint/resolver/intrinsic_table.inl
index 9e6749c..91e03c3 100644
--- a/src/tint/resolver/intrinsic_table.inl
+++ b/src/tint/resolver/intrinsic_table.inl
@@ -23,7 +23,7 @@
 // clang-format off
 
 /// TypeMatcher for 'type bool'
-/// @see src/tint/intrinsics.def:73:6
+/// @see src/tint/intrinsics.def:76:6
 class Bool : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -50,7 +50,7 @@
 }
 
 /// TypeMatcher for 'type fa'
-/// @see src/tint/intrinsics.def:74:48
+/// @see src/tint/intrinsics.def:77:48
 class Fa : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -79,7 +79,7 @@
 }
 
 /// TypeMatcher for 'type ia'
-/// @see src/tint/intrinsics.def:75:48
+/// @see src/tint/intrinsics.def:78:48
 class Ia : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -108,7 +108,7 @@
 }
 
 /// TypeMatcher for 'type i32'
-/// @see src/tint/intrinsics.def:76:21
+/// @see src/tint/intrinsics.def:79:21
 class I32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -135,7 +135,7 @@
 }
 
 /// TypeMatcher for 'type u32'
-/// @see src/tint/intrinsics.def:77:21
+/// @see src/tint/intrinsics.def:80:21
 class U32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -162,7 +162,7 @@
 }
 
 /// TypeMatcher for 'type f32'
-/// @see src/tint/intrinsics.def:78:21
+/// @see src/tint/intrinsics.def:81:21
 class F32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -189,7 +189,7 @@
 }
 
 /// TypeMatcher for 'type f16'
-/// @see src/tint/intrinsics.def:79:21
+/// @see src/tint/intrinsics.def:82:21
 class F16 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -216,7 +216,7 @@
 }
 
 /// TypeMatcher for 'type vec2'
-/// @see src/tint/intrinsics.def:80:6
+/// @see src/tint/intrinsics.def:83:6
 class Vec2 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -249,7 +249,7 @@
 }
 
 /// TypeMatcher for 'type vec3'
-/// @see src/tint/intrinsics.def:81:6
+/// @see src/tint/intrinsics.def:84:6
 class Vec3 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -282,7 +282,7 @@
 }
 
 /// TypeMatcher for 'type vec4'
-/// @see src/tint/intrinsics.def:82:6
+/// @see src/tint/intrinsics.def:85:6
 class Vec4 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -315,7 +315,7 @@
 }
 
 /// TypeMatcher for 'type mat2x2'
-/// @see src/tint/intrinsics.def:83:6
+/// @see src/tint/intrinsics.def:86:6
 class Mat2X2 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -348,7 +348,7 @@
 }
 
 /// TypeMatcher for 'type mat2x3'
-/// @see src/tint/intrinsics.def:84:6
+/// @see src/tint/intrinsics.def:87:6
 class Mat2X3 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -381,7 +381,7 @@
 }
 
 /// TypeMatcher for 'type mat2x4'
-/// @see src/tint/intrinsics.def:85:6
+/// @see src/tint/intrinsics.def:88:6
 class Mat2X4 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -414,7 +414,7 @@
 }
 
 /// TypeMatcher for 'type mat3x2'
-/// @see src/tint/intrinsics.def:86:6
+/// @see src/tint/intrinsics.def:89:6
 class Mat3X2 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -447,7 +447,7 @@
 }
 
 /// TypeMatcher for 'type mat3x3'
-/// @see src/tint/intrinsics.def:87:6
+/// @see src/tint/intrinsics.def:90:6
 class Mat3X3 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -480,7 +480,7 @@
 }
 
 /// TypeMatcher for 'type mat3x4'
-/// @see src/tint/intrinsics.def:88:6
+/// @see src/tint/intrinsics.def:91:6
 class Mat3X4 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -513,7 +513,7 @@
 }
 
 /// TypeMatcher for 'type mat4x2'
-/// @see src/tint/intrinsics.def:89:6
+/// @see src/tint/intrinsics.def:92:6
 class Mat4X2 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -546,7 +546,7 @@
 }
 
 /// TypeMatcher for 'type mat4x3'
-/// @see src/tint/intrinsics.def:90:6
+/// @see src/tint/intrinsics.def:93:6
 class Mat4X3 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -579,7 +579,7 @@
 }
 
 /// TypeMatcher for 'type mat4x4'
-/// @see src/tint/intrinsics.def:91:6
+/// @see src/tint/intrinsics.def:94:6
 class Mat4X4 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -612,7 +612,7 @@
 }
 
 /// TypeMatcher for 'type vec'
-/// @see src/tint/intrinsics.def:92:34
+/// @see src/tint/intrinsics.def:95:34
 class Vec : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -653,7 +653,7 @@
 }
 
 /// TypeMatcher for 'type mat'
-/// @see src/tint/intrinsics.def:93:34
+/// @see src/tint/intrinsics.def:96:34
 class Mat : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -700,7 +700,7 @@
 }
 
 /// TypeMatcher for 'type ptr'
-/// @see src/tint/intrinsics.def:94:6
+/// @see src/tint/intrinsics.def:97:6
 class Ptr : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -745,7 +745,7 @@
 }
 
 /// TypeMatcher for 'type atomic'
-/// @see src/tint/intrinsics.def:95:6
+/// @see src/tint/intrinsics.def:98:6
 class Atomic : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -778,7 +778,7 @@
 }
 
 /// TypeMatcher for 'type array'
-/// @see src/tint/intrinsics.def:96:6
+/// @see src/tint/intrinsics.def:99:6
 class Array : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -811,7 +811,7 @@
 }
 
 /// TypeMatcher for 'type sampler'
-/// @see src/tint/intrinsics.def:97:6
+/// @see src/tint/intrinsics.def:100:6
 class Sampler : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -838,7 +838,7 @@
 }
 
 /// TypeMatcher for 'type sampler_comparison'
-/// @see src/tint/intrinsics.def:98:6
+/// @see src/tint/intrinsics.def:101:6
 class SamplerComparison : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -865,7 +865,7 @@
 }
 
 /// TypeMatcher for 'type texture_1d'
-/// @see src/tint/intrinsics.def:99:6
+/// @see src/tint/intrinsics.def:102:6
 class Texture1D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -898,7 +898,7 @@
 }
 
 /// TypeMatcher for 'type texture_2d'
-/// @see src/tint/intrinsics.def:100:6
+/// @see src/tint/intrinsics.def:103:6
 class Texture2D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -931,7 +931,7 @@
 }
 
 /// TypeMatcher for 'type texture_2d_array'
-/// @see src/tint/intrinsics.def:101:6
+/// @see src/tint/intrinsics.def:104:6
 class Texture2DArray : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -964,7 +964,7 @@
 }
 
 /// TypeMatcher for 'type texture_3d'
-/// @see src/tint/intrinsics.def:102:6
+/// @see src/tint/intrinsics.def:105:6
 class Texture3D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -997,7 +997,7 @@
 }
 
 /// TypeMatcher for 'type texture_cube'
-/// @see src/tint/intrinsics.def:103:6
+/// @see src/tint/intrinsics.def:106:6
 class TextureCube : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1030,7 +1030,7 @@
 }
 
 /// TypeMatcher for 'type texture_cube_array'
-/// @see src/tint/intrinsics.def:104:6
+/// @see src/tint/intrinsics.def:107:6
 class TextureCubeArray : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1063,7 +1063,7 @@
 }
 
 /// TypeMatcher for 'type texture_multisampled_2d'
-/// @see src/tint/intrinsics.def:105:6
+/// @see src/tint/intrinsics.def:108:6
 class TextureMultisampled2D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1096,7 +1096,7 @@
 }
 
 /// TypeMatcher for 'type texture_depth_2d'
-/// @see src/tint/intrinsics.def:106:6
+/// @see src/tint/intrinsics.def:109:6
 class TextureDepth2D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1123,7 +1123,7 @@
 }
 
 /// TypeMatcher for 'type texture_depth_2d_array'
-/// @see src/tint/intrinsics.def:107:6
+/// @see src/tint/intrinsics.def:110:6
 class TextureDepth2DArray : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1150,7 +1150,7 @@
 }
 
 /// TypeMatcher for 'type texture_depth_cube'
-/// @see src/tint/intrinsics.def:108:6
+/// @see src/tint/intrinsics.def:111:6
 class TextureDepthCube : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1177,7 +1177,7 @@
 }
 
 /// TypeMatcher for 'type texture_depth_cube_array'
-/// @see src/tint/intrinsics.def:109:6
+/// @see src/tint/intrinsics.def:112:6
 class TextureDepthCubeArray : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1204,7 +1204,7 @@
 }
 
 /// TypeMatcher for 'type texture_depth_multisampled_2d'
-/// @see src/tint/intrinsics.def:110:6
+/// @see src/tint/intrinsics.def:113:6
 class TextureDepthMultisampled2D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1231,7 +1231,7 @@
 }
 
 /// TypeMatcher for 'type texture_storage_1d'
-/// @see src/tint/intrinsics.def:111:6
+/// @see src/tint/intrinsics.def:114:6
 class TextureStorage1D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1270,7 +1270,7 @@
 }
 
 /// TypeMatcher for 'type texture_storage_2d'
-/// @see src/tint/intrinsics.def:112:6
+/// @see src/tint/intrinsics.def:115:6
 class TextureStorage2D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1309,7 +1309,7 @@
 }
 
 /// TypeMatcher for 'type texture_storage_2d_array'
-/// @see src/tint/intrinsics.def:113:6
+/// @see src/tint/intrinsics.def:116:6
 class TextureStorage2DArray : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1348,7 +1348,7 @@
 }
 
 /// TypeMatcher for 'type texture_storage_3d'
-/// @see src/tint/intrinsics.def:114:6
+/// @see src/tint/intrinsics.def:117:6
 class TextureStorage3D : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1387,7 +1387,7 @@
 }
 
 /// TypeMatcher for 'type texture_external'
-/// @see src/tint/intrinsics.def:115:6
+/// @see src/tint/intrinsics.def:118:6
 class TextureExternal : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1414,7 +1414,7 @@
 }
 
 /// TypeMatcher for 'type __modf_result'
-/// @see src/tint/intrinsics.def:117:6
+/// @see src/tint/intrinsics.def:120:6
 class ModfResult : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1441,7 +1441,7 @@
 }
 
 /// TypeMatcher for 'type __modf_result_vec'
-/// @see src/tint/intrinsics.def:118:39
+/// @see src/tint/intrinsics.def:121:39
 class ModfResultVec : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1476,7 +1476,7 @@
 }
 
 /// TypeMatcher for 'type __frexp_result'
-/// @see src/tint/intrinsics.def:119:6
+/// @see src/tint/intrinsics.def:122:6
 class FrexpResult : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1503,7 +1503,7 @@
 }
 
 /// TypeMatcher for 'type __frexp_result_vec'
-/// @see src/tint/intrinsics.def:120:40
+/// @see src/tint/intrinsics.def:123:40
 class FrexpResultVec : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1538,7 +1538,7 @@
 }
 
 /// TypeMatcher for 'type __atomic_compare_exchange_result'
-/// @see src/tint/intrinsics.def:122:6
+/// @see src/tint/intrinsics.def:125:6
 class AtomicCompareExchangeResult : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules.
@@ -1571,7 +1571,7 @@
 }
 
 /// TypeMatcher for 'match abstract_or_scalar'
-/// @see src/tint/intrinsics.def:130:7
+/// @see src/tint/intrinsics.def:133:7
 class AbstractOrScalar : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1621,7 +1621,7 @@
 }
 
 /// TypeMatcher for 'match scalar'
-/// @see src/tint/intrinsics.def:131:7
+/// @see src/tint/intrinsics.def:134:7
 class Scalar : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1665,7 +1665,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_f32'
-/// @see src/tint/intrinsics.def:132:7
+/// @see src/tint/intrinsics.def:135:7
 class ScalarNoF32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1706,7 +1706,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_f16'
-/// @see src/tint/intrinsics.def:133:7
+/// @see src/tint/intrinsics.def:136:7
 class ScalarNoF16 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1747,7 +1747,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_i32'
-/// @see src/tint/intrinsics.def:134:7
+/// @see src/tint/intrinsics.def:137:7
 class ScalarNoI32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1788,7 +1788,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_u32'
-/// @see src/tint/intrinsics.def:135:7
+/// @see src/tint/intrinsics.def:138:7
 class ScalarNoU32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1829,7 +1829,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_bool'
-/// @see src/tint/intrinsics.def:136:7
+/// @see src/tint/intrinsics.def:139:7
 class ScalarNoBool : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1870,7 +1870,7 @@
 }
 
 /// TypeMatcher for 'match fia_fi32_f16'
-/// @see src/tint/intrinsics.def:137:7
+/// @see src/tint/intrinsics.def:140:7
 class FiaFi32F16 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1914,7 +1914,7 @@
 }
 
 /// TypeMatcher for 'match fia_fiu32'
-/// @see src/tint/intrinsics.def:138:7
+/// @see src/tint/intrinsics.def:141:7
 class FiaFiu32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1958,7 +1958,7 @@
 }
 
 /// TypeMatcher for 'match fa_f32'
-/// @see src/tint/intrinsics.def:139:7
+/// @see src/tint/intrinsics.def:142:7
 class FaF32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1993,7 +1993,7 @@
 }
 
 /// TypeMatcher for 'match fa_f32_f16'
-/// @see src/tint/intrinsics.def:140:7
+/// @see src/tint/intrinsics.def:143:7
 class FaF32F16 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -2031,7 +2031,7 @@
 }
 
 /// TypeMatcher for 'match ia_iu32'
-/// @see src/tint/intrinsics.def:141:7
+/// @see src/tint/intrinsics.def:144:7
 class IaIu32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -2069,7 +2069,7 @@
 }
 
 /// TypeMatcher for 'match fiu32_f16'
-/// @see src/tint/intrinsics.def:142:7
+/// @see src/tint/intrinsics.def:145:7
 class Fiu32F16 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -2110,7 +2110,7 @@
 }
 
 /// TypeMatcher for 'match fiu32'
-/// @see src/tint/intrinsics.def:143:7
+/// @see src/tint/intrinsics.def:146:7
 class Fiu32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -2148,7 +2148,7 @@
 }
 
 /// TypeMatcher for 'match fi32_f16'
-/// @see src/tint/intrinsics.def:144:7
+/// @see src/tint/intrinsics.def:147:7
 class Fi32F16 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -2186,7 +2186,7 @@
 }
 
 /// TypeMatcher for 'match fi32'
-/// @see src/tint/intrinsics.def:145:7
+/// @see src/tint/intrinsics.def:148:7
 class Fi32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -2221,7 +2221,7 @@
 }
 
 /// TypeMatcher for 'match f32_f16'
-/// @see src/tint/intrinsics.def:146:7
+/// @see src/tint/intrinsics.def:149:7
 class F32F16 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -2256,7 +2256,7 @@
 }
 
 /// TypeMatcher for 'match iu32'
-/// @see src/tint/intrinsics.def:147:7
+/// @see src/tint/intrinsics.def:150:7
 class Iu32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -2291,7 +2291,7 @@
 }
 
 /// EnumMatcher for 'match f32_texel_format'
-/// @see src/tint/intrinsics.def:158:7
+/// @see src/tint/intrinsics.def:161:7
 class F32TexelFormat : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2324,7 +2324,7 @@
 }
 
 /// EnumMatcher for 'match i32_texel_format'
-/// @see src/tint/intrinsics.def:165:7
+/// @see src/tint/intrinsics.def:168:7
 class I32TexelFormat : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2356,7 +2356,7 @@
 }
 
 /// EnumMatcher for 'match u32_texel_format'
-/// @see src/tint/intrinsics.def:171:7
+/// @see src/tint/intrinsics.def:174:7
 class U32TexelFormat : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2388,7 +2388,7 @@
 }
 
 /// EnumMatcher for 'match write'
-/// @see src/tint/intrinsics.def:178:7
+/// @see src/tint/intrinsics.def:181:7
 class Write : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2414,7 +2414,7 @@
 }
 
 /// EnumMatcher for 'match read_write'
-/// @see src/tint/intrinsics.def:179:7
+/// @see src/tint/intrinsics.def:182:7
 class ReadWrite : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2440,7 +2440,7 @@
 }
 
 /// EnumMatcher for 'match function_private_workgroup'
-/// @see src/tint/intrinsics.def:181:7
+/// @see src/tint/intrinsics.def:184:7
 class FunctionPrivateWorkgroup : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2470,7 +2470,7 @@
 }
 
 /// EnumMatcher for 'match workgroup_or_storage'
-/// @see src/tint/intrinsics.def:185:7
+/// @see src/tint/intrinsics.def:188:7
 class WorkgroupOrStorage : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2499,7 +2499,7 @@
 }
 
 /// EnumMatcher for 'match storage'
-/// @see src/tint/intrinsics.def:188:7
+/// @see src/tint/intrinsics.def:191:7
 class Storage : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
diff --git a/src/tint/resolver/intrinsic_table.inl.tmpl b/src/tint/resolver/intrinsic_table.inl.tmpl
index e78e5cb..340ae45 100644
--- a/src/tint/resolver/intrinsic_table.inl.tmpl
+++ b/src/tint/resolver/intrinsic_table.inl.tmpl
@@ -3,6 +3,9 @@
 Template file for use with tools/src/cmd/gen to generate builtin_table.inl
 Used by BuiltinTable.cc for builtin overload resolution.
 
+To update the generated file, run:
+    ./tools/run gen
+
 See:
 * tools/src/cmd/gen for structures used by this template
 * https://golang.org/pkg/text/template/ for documentation on the template syntax
diff --git a/src/tint/sem/builtin_type.cc.tmpl b/src/tint/sem/builtin_type.cc.tmpl
index cc1e682..5737d8d 100644
--- a/src/tint/sem/builtin_type.cc.tmpl
+++ b/src/tint/sem/builtin_type.cc.tmpl
@@ -2,6 +2,9 @@
 --------------------------------------------------------------------------------
 Template file for use with tools/src/cmd/gen to generate builtin_type.cc
 
+To update the generated file, run:
+    ./tools/run gen
+
 See:
 * tools/src/cmd/gen for structures used by this template
 * https://golang.org/pkg/text/template/ for documentation on the template syntax
diff --git a/src/tint/sem/builtin_type.h.tmpl b/src/tint/sem/builtin_type.h.tmpl
index 9202a3d..84ceffc 100644
--- a/src/tint/sem/builtin_type.h.tmpl
+++ b/src/tint/sem/builtin_type.h.tmpl
@@ -2,6 +2,9 @@
 --------------------------------------------------------------------------------
 Template file for use with tools/src/cmd/gen to generate builtin_type.h
 
+To update the generated file, run:
+    ./tools/run gen
+
 See:
 * tools/src/cmd/gen for structures used by this template
 * https://golang.org/pkg/text/template/ for documentation on the template syntax
diff --git a/src/tint/sem/parameter_usage.cc.tmpl b/src/tint/sem/parameter_usage.cc.tmpl
index 7bb8b2e..62cb42e 100644
--- a/src/tint/sem/parameter_usage.cc.tmpl
+++ b/src/tint/sem/parameter_usage.cc.tmpl
@@ -2,6 +2,9 @@
 --------------------------------------------------------------------------------
 Template file for use with tools/src/cmd/gen to generate parameter_usage.cc
 
+To update the generated file, run:
+    ./tools/run gen
+
 See:
 * tools/src/cmd/gen for structures used by this template
 * https://golang.org/pkg/text/template/ for documentation on the template syntax
diff --git a/src/tint/sem/parameter_usage.h.tmpl b/src/tint/sem/parameter_usage.h.tmpl
index 5aaa8b4..7c09805 100644
--- a/src/tint/sem/parameter_usage.h.tmpl
+++ b/src/tint/sem/parameter_usage.h.tmpl
@@ -2,6 +2,9 @@
 --------------------------------------------------------------------------------
 Template file for use with tools/src/cmd/gen to generate parameter_usage.h
 
+To update the generated file, run:
+    ./tools/run gen
+
 See:
 * tools/src/cmd/gen for structures used by this template
 * https://golang.org/pkg/text/template/ for documentation on the template syntax
diff --git a/src/tint/templates/enums.tmpl.inc b/src/tint/templates/enums.tmpl.inc
new file mode 100644
index 0000000..d05d763
--- /dev/null
+++ b/src/tint/templates/enums.tmpl.inc
@@ -0,0 +1,160 @@
+{{- /* ------------------------------------------------------------------ */ -}}
+{{-                         define "EnumCase"                                -}}
+{{- /* Prints the 'Enum::kEntry' name for the provided  sem.EnumEntry     */ -}}
+{{- /* argument.                                                          */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{PascalCase $.Enum.Name}}::k{{PascalCase $.Name}}
+{{- end -}}
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{-                         define "DeclareEnum"                             -}}
+{{- /* Declares the 'enum class' for the provided sem.Enum argument.      */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum    := PascalCase $.Name -}}
+enum class {{$enum}} {
+    kInvalid,
+{{-   range $entry := $.Entries }}
+    k{{PascalCase $entry.Name}},{{if $entry.IsInternal}}  // Tint-internal enum entry - not parsed{{end}}
+{{-   end }}
+};
+
+/// @param out the std::ostream to write to
+/// @param value the {{$enum}}
+/// @returns `out` so calls can be chained
+std::ostream& operator<<(std::ostream& out, {{$enum}} value);
+
+/// Parse{{$enum}} parses a {{$enum}} from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or {{$enum}}::kInvalid if the string could not be parsed.
+{{$enum}} Parse{{$enum}}(std::string_view str);
+
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{-                          define "ParseEnum"                              -}}
+{{- /* Implements the 'ParseEnum' function for the provided sem.Enum      */ -}}
+{{- /* argument.                                                          */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum    := PascalCase $.Name -}}
+/// Parse{{$enum}} parses a {{$enum}} from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or {{$enum}}::kInvalid if the string could not be parsed.
+{{$enum}} Parse{{$enum}}(std::string_view str) {
+{{-   range $entry := $.PublicEntries }}
+    if (str == "{{$entry.Name}}") {
+        return {{template "EnumCase" $entry}};
+    }
+{{-   end }}
+    return {{$enum}}::kInvalid;
+}
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{-                         define "EnumOStream"                             -}}
+{{- /* Implements the std::ostream 'operator<<()' function to print the   */ -}}
+{{- /* provided sem.Enum.                                                 */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum    := PascalCase $.Name -}}
+std::ostream& operator<<(std::ostream& out, {{$enum}} value) {
+    switch (value) {
+        case {{$enum}}::kInvalid:
+            return out << "invalid";
+{{-   range $entry := $.Entries }}
+        case {{template "EnumCase" $entry}}:
+            return out << "{{$entry.Name}}";
+{{-   end }}
+    }
+    return out << "<unknown>";
+}
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{-                        define "TestParsePrintEnum"                       -}}
+{{- /* Implements unit tests for parsing and printing the provided        */ -}}
+{{- /* sem.Enum argument.                                                 */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum    := PascalCase $.Name -}}
+namespace parse_print_tests {
+
+struct Case {
+    const char* string;
+    {{$enum}} value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+    return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+{{-   range $entry := $.PublicEntries }}
+    {"{{$entry.Name}}", {{template "EnumCase" $entry}}},
+{{-   end }}
+};
+
+static constexpr Case kInvalidCases[] = {
+{{-   $exclude := $.NameSet -}}
+{{-   range $entry := $.PublicEntries }}
+    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kInvalid},
+    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kInvalid},
+    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kInvalid},
+{{-   end }}
+};
+
+using {{$enum}}ParseTest = testing::TestWithParam<Case>;
+
+TEST_P({{$enum}}ParseTest, Parse) {
+    const char* string = GetParam().string;
+    {{$enum}} expect = GetParam().value;
+    EXPECT_EQ(expect, Parse{{$enum}}(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, {{$enum}}ParseTest, testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases, {{$enum}}ParseTest, testing::ValuesIn(kInvalidCases));
+
+using {{$enum}}PrintTest = testing::TestWithParam<Case>;
+
+TEST_P({{$enum}}PrintTest, Print) {
+    {{$enum}} value = GetParam().value;
+    const char* expect = GetParam().string;
+    EXPECT_EQ(expect, utils::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, {{$enum}}PrintTest, testing::ValuesIn(kValidCases));
+
+}  // namespace parse_print_tests
+
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{-                     define "BenchmarkParseEnum"                          -}}
+{{- /* Implements a micro-benchmark for parsing the provided sem.Enum     */ -}}
+{{- /* argument.                                                          */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum    := PascalCase $.Name -}}
+void {{$enum}}Parser(::benchmark::State& state) {
+    std::array kStrings{
+{{-   $exclude := $.NameSet -}}
+{{-   range $entry := $.PublicEntries }}
+        "{{Scramble $entry.Name $exclude}}",
+        "{{Scramble $entry.Name $exclude}}",
+        "{{Scramble $entry.Name $exclude}}",
+        "{{$entry.Name}}",
+        "{{Scramble $entry.Name $exclude}}",
+        "{{Scramble $entry.Name $exclude}}",
+        "{{Scramble $entry.Name $exclude}}",
+{{-   end }}
+    };
+    for (auto _ : state) {
+        for (auto& str : kStrings) {
+            auto result = Parse{{$enum}}(str);
+            benchmark::DoNotOptimize(result);
+        }
+    }
+}
+
+BENCHMARK({{$enum}}Parser);
+{{- end -}}