Merge pull request #96 from reventlov/fix_iterator_bug

Fix array iterators when the array view is a temporary object.
diff --git a/compiler/back_end/cpp/BUILD b/compiler/back_end/cpp/BUILD
index c67b686..3d31ec2 100644
--- a/compiler/back_end/cpp/BUILD
+++ b/compiler/back_end/cpp/BUILD
@@ -35,12 +35,19 @@
 )
 
 py_library(
+    name = "attributes",
+    srcs = ["attributes.py"],
+    deps = []
+)
+
+py_library(
     name = "header_generator",
     srcs = ["header_generator.py"],
     data = [
         "generated_code_templates",
     ],
     deps = [
+        ":attributes",
         "//compiler/back_end/util:code_template",
         "//compiler/util:attribute_util",
         "//compiler/util:ir_pb2",
@@ -115,6 +122,17 @@
 )
 
 emboss_cc_test(
+    name = "enum_case_test",
+    srcs = [
+        "testcode/enum_case_test.cc",
+    ],
+    deps = [
+        "//testdata:enum_case_emboss",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_test(
     name = "explicit_sizes_test",
     srcs = [
         "testcode/explicit_sizes_test.cc",
diff --git a/compiler/back_end/cpp/attributes.py b/compiler/back_end/cpp/attributes.py
new file mode 100644
index 0000000..86efb72
--- /dev/null
+++ b/compiler/back_end/cpp/attributes.py
@@ -0,0 +1,57 @@
+# Copyright 2023 Google LLC
+#
+# 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
+#
+#     https://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.
+
+"""Attributes in the C++ backend and associated metadata."""
+
+from enum import Enum
+from compiler.util import attribute_util
+
+
+class Attribute(str, Enum):
+    """Attributes available in the C++ backend."""
+    NAMESPACE = "namespace"
+    ENUM_CASE = "enum_case"
+
+
+# Types associated with C++ backend attributes.
+TYPES = {
+    Attribute.NAMESPACE: attribute_util.STRING,
+    Attribute.ENUM_CASE: attribute_util.STRING,
+}
+
+
+class Scope(set[tuple[Attribute, bool]], Enum):
+    """Allowed scopes for C++ backend attributes.
+
+    Each entry is a set of (Attribute, default?) tuples, the first value being
+    the attribute itself, the second value being a boolean value indicating
+    whether the attribute is allowed to be defaulted in that scope."""
+    BITS = {
+        # Bits may contain an enum definition.
+        (Attribute.ENUM_CASE, True)
+    }
+    ENUM = {
+        (Attribute.ENUM_CASE, True),
+    }
+    ENUM_VALUE = {
+        (Attribute.ENUM_CASE, False),
+    }
+    MODULE = {
+        (Attribute.NAMESPACE, False),
+        (Attribute.ENUM_CASE, True),
+    },
+    STRUCT = {
+        # Struct may contain an enum definition.
+        (Attribute.ENUM_CASE, True),
+    }
diff --git a/compiler/back_end/cpp/generated_code_templates b/compiler/back_end/cpp/generated_code_templates
index 4541bdb..4554b39 100644
--- a/compiler/back_end/cpp/generated_code_templates
+++ b/compiler/back_end/cpp/generated_code_templates
@@ -37,7 +37,9 @@
 
 $_includes_$
 
+// NOLINTBEGIN
 $_body_$
+// NOLINTEND
 
 #endif  // $_header_guard_$
 
@@ -840,12 +842,12 @@
 
 // ** enum_from_name_case ** ///////////////////////////////////////////////////
     if (!strcmp("$_name_$", emboss_reserved_local_name)) {
-      *emboss_reserved_local_result = $_enum_$::$_name_$;
+      *emboss_reserved_local_result = $_enum_$::$_value_$;
       return true;
     }
 
 // ** name_from_enum_case ** ///////////////////////////////////////////////////
-      case $_enum_$::$_name_$: return "$_name_$";
+      case $_enum_$::$_value_$: return "$_name_$";
 
 // ** enum_is_known_case ** ////////////////////////////////////////////////////
       case $_enum_$::$_name_$: return true;
diff --git a/compiler/back_end/cpp/header_generator.py b/compiler/back_end/cpp/header_generator.py
index e467284..922d80b 100644
--- a/compiler/back_end/cpp/header_generator.py
+++ b/compiler/back_end/cpp/header_generator.py
@@ -22,11 +22,14 @@
 import pkgutil
 import re
 
+from compiler.back_end.cpp import attributes
 from compiler.back_end.util import code_template
 from compiler.util import attribute_util
+from compiler.util import error
 from compiler.util import ir_pb2
 from compiler.util import ir_util
 from compiler.util import name_conversion
+from compiler.util import traverse_ir
 
 _TEMPLATES = code_template.parse_templates(pkgutil.get_data(
     "compiler.back_end.cpp",
@@ -63,9 +66,41 @@
 # Emboss C++ support classes.
 _SUPPORT_NAMESPACE = "::emboss::support"
 
+# Regex matching a C++ namespace component. Captures component name.
+_NS_COMPONENT_RE = r"(?:^\s*|::)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\s*$|::)"
+# Regex matching a full C++ namespace (at least one namespace component).
+_NS_RE = fr"^\s*(?:{_NS_COMPONENT_RE})+\s*$"
+# Regex matching an empty C++ namespace.
+_NS_EMPTY_RE = r"^\s*$"
+# Regex matching only the global C++ namespace.
+_NS_GLOBAL_RE = r"^\s*::\s*$"
+
 # TODO(bolms): This should be a command-line flag.
 _PRELUDE_INCLUDE_FILE = "runtime/cpp/emboss_prelude.h"
 
+# Cases allowed in the `enum_case` attribute.
+_SUPPORTED_ENUM_CASES = ("SHOUTY_CASE", "kCamelCase")
+
+# Verify that all supported enum cases have valid, implemented conversions.
+for _enum_case in _SUPPORTED_ENUM_CASES:
+  assert name_conversion.is_case_conversion_supported("SHOUTY_CASE", _enum_case)
+
+
+def _get_namespace_components(namespace):
+  """Gets the components of a C++ namespace
+
+  Examples:
+    "::some::name::detail" -> ["some", "name", "detail"]
+    "product::name" -> ["product", "name"]
+    "simple" -> ["simple"]
+
+  Arguments:
+    namespace: A string containing the namespace. May be fully-qualified.
+
+  Returns:
+    A list of strings, one per namespace component."""
+  return re.findall(_NS_COMPONENT_RE, namespace)
+
 
 def _get_module_namespace(module):
   """Returns the C++ namespace of the module, as a list of components.
@@ -82,19 +117,7 @@
     namespace = namespace_attr.string_constant.text
   else:
     namespace = "emboss_generated_code"
-  if namespace[0:2] == "::":
-    # If the user explicitly specified the leading "::", trim it off: it will be
-    # re-added later, when the namespace is used as a prefix (as opposed to
-    # "namespace foo { }").
-    namespace = namespace[2:]
-  namespace_list = namespace.split("::")
-  for namespace_component in namespace_list:
-    assert re.match("[a-zA-Z_][a-zA-Z0-9_]*", namespace_component), (
-        "Bad namespace '{}'".format(namespace))
-    assert namespace_component not in _CPP_RESERVED_WORDS, (
-        "Reserved word '{}' is not allowed as a namespace component.".format(
-            namespace_component))
-  return namespace_list
+  return _get_namespace_components(namespace)
 
 
 def _cpp_string_escape(string):
@@ -1232,6 +1255,65 @@
           subtype_method_definitions + method_definitions)
 
 
+def _split_enum_case_values_into_spans(enum_case_value):
+  """Yields spans containing each enum case in an enum_case attribute value.
+
+  Each span is of the form (start, end), which is the start and end position
+  relative to the beginning of the enum_case_value string. To keep the grammar
+  of this attribute simple, this only splits on delimiters and trims whitespace
+  for each case.
+
+  Example: 'SHOUTY_CASE, kCamelCase' -> [(0, 11), (13, 23)]"""
+  # Scan the string from left to right, finding commas and trimming whitespace.
+  # This is essentially equivalent to (x.trim() fror x in str.split(','))
+  # except that this yields spans within the string rather than the strings
+  # themselves, and no span is yielded for a trailing comma.
+  start, end = 0, len(enum_case_value)
+  while start <= end:
+    # Find a ',' delimiter to split on
+    delimiter = enum_case_value.find(',', start, end)
+    if delimiter < 0:
+      delimiter = end
+
+    substr_start = start
+    substr_end = delimiter
+
+    # Drop leading whitespace
+    while (substr_start < substr_end and
+           enum_case_value[substr_start].isspace()):
+      substr_start += 1
+    # Drop trailing whitespace
+    while (substr_start < substr_end and
+           enum_case_value[substr_end - 1].isspace()):
+      substr_end -= 1
+
+    # Skip a trailing comma
+    if substr_start == end and start != 0:
+      break
+
+    yield substr_start, substr_end
+    start = delimiter + 1
+
+
+def _split_enum_case_values(enum_case_value):
+  """Returns all enum cases in an enum case value.
+
+  Example: 'SHOUTY_CASE, kCamelCase' -> ['SHOUTY_CASE', 'kCamelCase']"""
+  return [enum_case_value[start:end] for start, end
+          in _split_enum_case_values_into_spans(enum_case_value)]
+
+
+def _get_enum_value_names(enum_value):
+  """Determines one or more enum names based on attributes"""
+  cases = ["SHOUTY_CASE"]
+  name = enum_value.name.name.text
+  if enum_case := ir_util.get_attribute(enum_value.attribute,
+                                        attributes.Attribute.ENUM_CASE):
+    cases = _split_enum_case_values(enum_case.string_constant.text)
+  return [name_conversion.convert_case("SHOUTY_CASE", case, name)
+            for case in cases]
+
+
 def _generate_enum_definition(type_ir):
   """Generates C++ for an Emboss enum."""
   enum_values = []
@@ -1244,24 +1326,32 @@
   enum_type = _cpp_integer_type_for_enum(max_bits, is_signed)
   for value in type_ir.enumeration.value:
     numeric_value = ir_util.constant_value(value.value)
-    enum_values.append(
-        code_template.format_template(_TEMPLATES.enum_value,
-                                      name=value.name.name.text,
-                                      value=_render_integer(numeric_value)))
-    enum_from_string_statements.append(
-        code_template.format_template(_TEMPLATES.enum_from_name_case,
-                                      enum=type_ir.name.name.text,
-                                      name=value.name.name.text))
-    if numeric_value not in previously_seen_numeric_values:
-      string_from_enum_statements.append(
-          code_template.format_template(_TEMPLATES.name_from_enum_case,
+    enum_value_names = _get_enum_value_names(value)
+
+    for enum_value_name in enum_value_names:
+      enum_values.append(
+          code_template.format_template(_TEMPLATES.enum_value,
+                                        name=enum_value_name,
+                                        value=_render_integer(numeric_value)))
+
+      enum_from_string_statements.append(
+          code_template.format_template(_TEMPLATES.enum_from_name_case,
                                         enum=type_ir.name.name.text,
+                                        value=enum_value_name,
                                         name=value.name.name.text))
-      enum_is_known_statements.append(
-          code_template.format_template(_TEMPLATES.enum_is_known_case,
-                                        enum=type_ir.name.name.text,
-                                        name=value.name.name.text))
-    previously_seen_numeric_values.add(numeric_value)
+
+      if numeric_value not in previously_seen_numeric_values:
+        string_from_enum_statements.append(
+            code_template.format_template(_TEMPLATES.name_from_enum_case,
+                                          enum=type_ir.name.name.text,
+                                          value=enum_value_name,
+                                          name=value.name.name.text))
+
+        enum_is_known_statements.append(
+            code_template.format_template(_TEMPLATES.enum_is_known_case,
+                                          enum=type_ir.name.name.text,
+                                          name=enum_value_name))
+      previously_seen_numeric_values.add(numeric_value)
   return (
       code_template.format_template(
           _TEMPLATES.enum_declaration,
@@ -1303,6 +1393,162 @@
   return no_double_underscore_path
 
 
+def _add_missing_enum_case_attribute_on_enum_value(enum_value, defaults):
+  """Adds an `enum_case` attribute if there isn't one but a default is set."""
+  if ir_util.get_attribute(enum_value.attribute,
+                           attributes.Attribute.ENUM_CASE) is None:
+    if attributes.Attribute.ENUM_CASE in defaults:
+      enum_value.attribute.extend([defaults[attributes.Attribute.ENUM_CASE]])
+
+
+def _propagate_defaults(ir, targets, ancestors, add_fn):
+  """Propagates default values
+
+  Traverses the IR to propagate default values to target nodes.
+
+  Arguments:
+    targets: A list of target IR types to add attributes to.
+    ancestors: Ancestor types which may contain the default values.
+    add_fn: Function to add the attribute. May use any parameter available in
+      fast_traverse_ir_top_down actions as well as `defaults` containing the
+      default attributes set by ancestors.
+
+  Returns:
+    None
+  """
+  traverse_ir.fast_traverse_ir_top_down(
+    ir, targets, add_fn,
+    incidental_actions={
+      ancestor: attribute_util.gather_default_attributes
+        for ancestor in ancestors
+    },
+    parameters={"defaults": {}})
+
+
+def _offset_source_location_column(source_location, offset):
+  """Adds offsets from the start column of the supplied source location
+
+  Returns a new source location with all of the same properties as the provided
+  source location, but with the columns modified by offsets from the original
+  start column.
+
+  Offset should be a tuple of (start, end), which are the offsets relative to
+  source_location.start.column to set the new start.column and end.column."""
+
+  new_location = ir_pb2.Location()
+  new_location.CopyFrom(source_location)
+  new_location.start.column = source_location.start.column + offset[0]
+  new_location.end.column = source_location.start.column + offset[1]
+
+  return new_location
+
+
+def _verify_namespace_attribute(attr, source_file_name, errors):
+  if attr.name.text != attributes.Attribute.NAMESPACE:
+    return
+  namespace_value = attr.value.string_constant
+  if not re.match(_NS_RE, namespace_value.text):
+    if re.match(_NS_EMPTY_RE, namespace_value.text):
+      errors.append([error.error(
+          source_file_name, namespace_value.source_location,
+          'Empty namespace value is not allowed.')])
+    elif re.match(_NS_GLOBAL_RE, namespace_value.text):
+      errors.append([error.error(
+          source_file_name, namespace_value.source_location,
+          'Global namespace is not allowed.')])
+    else:
+      errors.append([error.error(
+          source_file_name, namespace_value.source_location,
+          'Invalid namespace, must be a valid C++ namespace, such as "abc", '
+          '"abc::def", or "::abc::def::ghi" (ISO/IEC 14882:2017 '
+          'enclosing-namespace-specifier).')])
+    return
+  for word in _get_namespace_components(namespace_value.text):
+    if word in _CPP_RESERVED_WORDS:
+      errors.append([error.error(
+          source_file_name, namespace_value.source_location,
+          f'Reserved word "{word}" is not allowed as a namespace component.'
+      )])
+
+
+def _verify_enum_case_attribute(attr, source_file_name, errors):
+  """Verify that `enum_case` values are supported."""
+  if attr.name.text != attributes.Attribute.ENUM_CASE:
+    return
+
+  VALID_CASES = ', '.join(case for case in _SUPPORTED_ENUM_CASES)
+  enum_case_value = attr.value.string_constant
+  case_spans = _split_enum_case_values_into_spans(enum_case_value.text)
+  seen_cases = set()
+
+  for start, end in case_spans:
+    case_source_location = _offset_source_location_column(
+        enum_case_value.source_location, (start, end))
+    case = enum_case_value.text[start:end]
+
+    if start == end:
+      errors.append([error.error(
+          source_file_name, case_source_location,
+          'Empty enum case (or excess comma).')])
+      continue
+
+    if case in seen_cases:
+      errors.append([error.error(
+          source_file_name, case_source_location,
+          f'Duplicate enum case "{case}".')])
+      continue
+    seen_cases.add(case)
+
+    if case not in _SUPPORTED_ENUM_CASES:
+      errors.append([error.error(
+          source_file_name, case_source_location,
+          f'Unsupported enum case "{case}", '
+          f'supported cases are: {VALID_CASES}.')])
+
+
+def _verify_attribute_values(ir):
+  """Verify backend attribute values."""
+  errors = []
+
+  traverse_ir.fast_traverse_ir_top_down(
+      ir, [ir_pb2.Attribute], _verify_namespace_attribute,
+      parameters={"errors": errors})
+  traverse_ir.fast_traverse_ir_top_down(
+      ir, [ir_pb2.Attribute], _verify_enum_case_attribute,
+      parameters={"errors": errors})
+
+  return errors
+
+
+def _propagate_defaults_and_verify_attributes(ir):
+  """Verify attributes and ensure defaults are set when not overridden.
+
+  Returns a list of errors if there are errors present, or an empty list if
+  verification completed successfully."""
+  if errors := attribute_util.check_attributes_in_ir(
+          ir,
+          back_end="cpp",
+          types=attributes.TYPES,
+          module_attributes=attributes.Scope.MODULE,
+          struct_attributes=attributes.Scope.STRUCT,
+          bits_attributes=attributes.Scope.BITS,
+          enum_attributes=attributes.Scope.ENUM,
+          enum_value_attributes=attributes.Scope.ENUM_VALUE):
+    return errors
+
+  if errors := _verify_attribute_values(ir):
+    return errors
+
+  # Ensure defaults are set on EnumValues for `enum_case`.
+  _propagate_defaults(
+      ir,
+      targets=[ir_pb2.EnumValue],
+      ancestors=[ir_pb2.Module, ir_pb2.TypeDefinition],
+      add_fn=_add_missing_enum_case_attribute_on_enum_value)
+
+  return []
+
+
 def generate_header(ir):
   """Generates a C++ header from an Emboss module.
 
@@ -1315,11 +1561,7 @@
     module, or None, and `errors` is a possibly-empty list of error messages to
     display to the user.
   """
-  errors = attribute_util.check_attributes_in_ir(
-          ir,
-          back_end="cpp",
-          types={"namespace": attribute_util.STRING},
-          module_attributes={("namespace", False)})
+  errors = _propagate_defaults_and_verify_attributes(ir)
   if errors:
     return None, errors
   type_declarations = []
diff --git a/compiler/back_end/cpp/header_generator_test.py b/compiler/back_end/cpp/header_generator_test.py
index f20c1ae..e67ed50 100644
--- a/compiler/back_end/cpp/header_generator_test.py
+++ b/compiler/back_end/cpp/header_generator_test.py
@@ -18,9 +18,9 @@
 from compiler.back_end.cpp import header_generator
 from compiler.front_end import glue
 from compiler.util import error
+from compiler.util import ir_pb2
 from compiler.util import test_util
 
-
 def _make_ir_from_emb(emb_text, name="m.emb"):
   ir, unused_debug_info, errors = glue.parse_emboss_file(
       name,
@@ -51,6 +51,325 @@
                     "Unknown attribute '(cpp) byte_order' on module 'm.emb'.")
     ]], header_generator.generate_header(ir)[1])
 
+  def test_accepts_enum_case(self):
+    mod_ir = _make_ir_from_emb('[(cpp) $default enum_case: "kCamelCase"]')
+    self.assertEqual([], header_generator.generate_header(mod_ir)[1])
+    enum_ir = _make_ir_from_emb('enum Foo:\n'
+                                '  [(cpp) $default enum_case: "kCamelCase"]\n'
+                                '  BAR = 1\n'
+                                '  BAZ = 2\n')
+    self.assertEqual([], header_generator.generate_header(enum_ir)[1])
+    enum_value_ir = _make_ir_from_emb('enum Foo:\n'
+                                      '  BAR = 1  [(cpp) enum_case: "kCamelCase"]\n'
+                                      '  BAZ = 2\n'
+                                      '    [(cpp) enum_case: "kCamelCase"]\n')
+    self.assertEqual([], header_generator.generate_header(enum_value_ir)[1])
+    enum_in_struct_ir = _make_ir_from_emb('struct Outer:\n'
+                                          '  [(cpp) $default enum_case: "kCamelCase"]\n'
+                                          '  enum Inner:\n'
+                                          '    BAR = 1\n'
+                                          '    BAZ = 2\n')
+    self.assertEqual([], header_generator.generate_header(enum_in_struct_ir)[1])
+    enum_in_bits_ir = _make_ir_from_emb('bits Outer:\n'
+                                        '  [(cpp) $default enum_case: "kCamelCase"]\n'
+                                        '  enum Inner:\n'
+                                        '    BAR = 1\n'
+                                        '    BAZ = 2\n')
+    self.assertEqual([], header_generator.generate_header(enum_in_bits_ir)[1])
+    enum_ir = _make_ir_from_emb('enum Foo:\n'
+                                '  [(cpp) $default enum_case: "SHOUTY_CASE,"]\n'
+                                '  BAR = 1\n'
+                                '  BAZ = 2\n')
+    self.assertEqual([], header_generator.generate_header(enum_ir)[1])
+    enum_ir = _make_ir_from_emb('enum Foo:\n'
+                                '  [(cpp) $default enum_case: "SHOUTY_CASE   ,kCamelCase"]\n'
+                                '  BAR = 1\n'
+                                '  BAZ = 2\n')
+    self.assertEqual([], header_generator.generate_header(enum_ir)[1])
+
+  def test_rejects_bad_enum_case_at_start(self):
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: "SHORTY_CASE, kCamelCase"]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+    attr = ir.module[0].type[0].attribute[0]
+
+    bad_case_source_location = ir_pb2.Location()
+    bad_case_source_location.CopyFrom(attr.value.source_location)
+    # Location of SHORTY_CASE in the attribute line.
+    bad_case_source_location.start.column = 30
+    bad_case_source_location.end.column = 41
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Unsupported enum case "SHORTY_CASE", '
+                    'supported cases are: SHOUTY_CASE, kCamelCase.')
+    ]], header_generator.generate_header(ir)[1])
+
+  def test_rejects_bad_enum_case_in_middle(self):
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: "SHOUTY_CASE, bad_CASE, kCamelCase"]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+    attr = ir.module[0].type[0].attribute[0]
+
+    bad_case_source_location = ir_pb2.Location()
+    bad_case_source_location.CopyFrom(attr.value.source_location)
+    # Location of bad_CASE in the attribute line.
+    bad_case_source_location.start.column = 43
+    bad_case_source_location.end.column = 51
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Unsupported enum case "bad_CASE", '
+                    'supported cases are: SHOUTY_CASE, kCamelCase.')
+    ]], header_generator.generate_header(ir)[1])
+
+  def test_rejects_bad_enum_case_at_end(self):
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: "SHOUTY_CASE, kCamelCase, BAD_case"]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+    attr = ir.module[0].type[0].attribute[0]
+
+    bad_case_source_location = ir_pb2.Location()
+    bad_case_source_location.CopyFrom(attr.value.source_location)
+    # Location of BAD_case in the attribute line.
+    bad_case_source_location.start.column = 55
+    bad_case_source_location.end.column = 63
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Unsupported enum case "BAD_case", '
+                    'supported cases are: SHOUTY_CASE, kCamelCase.')
+    ]], header_generator.generate_header(ir)[1])
+
+  def test_rejects_duplicate_enum_case(self):
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: "SHOUTY_CASE, SHOUTY_CASE"]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+    attr = ir.module[0].type[0].attribute[0]
+
+    bad_case_source_location = ir_pb2.Location()
+    bad_case_source_location.CopyFrom(attr.value.source_location)
+    # Location of the second SHOUTY_CASE in the attribute line.
+    bad_case_source_location.start.column = 43
+    bad_case_source_location.end.column = 54
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Duplicate enum case "SHOUTY_CASE".')
+    ]], header_generator.generate_header(ir)[1])
+
+
+  def test_rejects_empty_enum_case(self):
+    # Double comma
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: "SHOUTY_CASE,, kCamelCase"]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+    attr = ir.module[0].type[0].attribute[0]
+
+    bad_case_source_location = ir_pb2.Location()
+    bad_case_source_location.CopyFrom(attr.value.source_location)
+    # Location of excess comma.
+    bad_case_source_location.start.column = 42
+    bad_case_source_location.end.column = 42
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Empty enum case (or excess comma).')
+    ]], header_generator.generate_header(ir)[1])
+
+    # Leading comma
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: ", SHOUTY_CASE, kCamelCase"]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+
+    bad_case_source_location.start.column = 30
+    bad_case_source_location.end.column = 30
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Empty enum case (or excess comma).')
+    ]], header_generator.generate_header(ir)[1])
+
+    # Excess trailing comma
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: "SHOUTY_CASE, kCamelCase,,"]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+
+    bad_case_source_location.start.column = 54
+    bad_case_source_location.end.column = 54
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Empty enum case (or excess comma).')
+    ]], header_generator.generate_header(ir)[1])
+
+    # Whitespace enum case
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: "SHOUTY_CASE,   , kCamelCase"]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+
+    bad_case_source_location.start.column = 45
+    bad_case_source_location.end.column = 45
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Empty enum case (or excess comma).')
+    ]], header_generator.generate_header(ir)[1])
+
+    # Empty enum_case string
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: ""]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+
+    bad_case_source_location.start.column = 30
+    bad_case_source_location.end.column = 30
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Empty enum case (or excess comma).')
+    ]], header_generator.generate_header(ir)[1])
+
+    # Whitespace enum_case string
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: "     "]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+
+    bad_case_source_location.start.column = 35
+    bad_case_source_location.end.column = 35
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Empty enum case (or excess comma).')
+    ]], header_generator.generate_header(ir)[1])
+
+    # One-character whitespace enum_case string
+    ir = _make_ir_from_emb('enum Foo:\n'
+                           '  [(cpp) $default enum_case: " "]\n'
+                           '  BAR = 1\n'
+                           '  BAZ = 2\n')
+
+    bad_case_source_location.start.column = 31
+    bad_case_source_location.end.column = 31
+
+    self.assertEqual([[
+        error.error("m.emb", bad_case_source_location,
+                    'Empty enum case (or excess comma).')
+    ]], header_generator.generate_header(ir)[1])
+
+  def test_accepts_namespace(self):
+    for test in [
+        '[(cpp) namespace: "basic"]\n',
+        '[(cpp) namespace: "multiple::components"]\n',
+        '[(cpp) namespace: "::absolute"]\n',
+        '[(cpp) namespace: "::fully::qualified"]\n',
+        '[(cpp) namespace: "CAN::Be::cAPITAL"]\n',
+        '[(cpp) namespace: "trailingNumbers54321"]\n',
+        '[(cpp) namespace: "containing4321numbers"]\n',
+        '[(cpp) namespace: "can_have_underscores"]\n',
+        '[(cpp) namespace: "_initial_underscore"]\n',
+        '[(cpp) namespace: "_initial::_underscore"]\n',
+        '[(cpp) namespace: "::_initial::_underscore"]\n',
+        '[(cpp) namespace: "trailing_underscore_"]\n',
+        '[(cpp) namespace: "trailing_::underscore_"]\n',
+        '[(cpp) namespace: "::trailing_::underscore_"]\n',
+        '[(cpp) namespace: " spaces "]\n',
+        '[(cpp) namespace: "with :: spaces"]\n',
+        '[(cpp) namespace: "   ::fully:: qualified :: with::spaces"]\n',
+    ]:
+      ir = _make_ir_from_emb(test)
+      self.assertEqual([], header_generator.generate_header(ir)[1])
+
+  def test_rejects_non_namespace_strings(self):
+    for test in [
+        '[(cpp) namespace: "5th::avenue"]\n',
+        '[(cpp) namespace: "can\'t::have::apostrophe"]\n',
+        '[(cpp) namespace: "cannot-have-dash"]\n',
+        '[(cpp) namespace: "no/slashes"]\n',
+        '[(cpp) namespace: "no\\\\slashes"]\n',
+        '[(cpp) namespace: "apostrophes*are*rejected"]\n',
+        '[(cpp) namespace: "avoid.dot"]\n',
+        '[(cpp) namespace: "does5+5"]\n',
+        '[(cpp) namespace: "=10"]\n',
+        '[(cpp) namespace: "?"]\n',
+        '[(cpp) namespace: "reject::spaces in::components"]\n',
+        '[(cpp) namespace: "totally::valid::but::extra         +"]\n',
+        '[(cpp) namespace: "totally::valid::but::extra         ::?"]\n',
+        '[(cpp) namespace: "< totally::valid::but::extra"]\n',
+        '[(cpp) namespace: "< ::totally::valid::but::extra"]\n',
+        '[(cpp) namespace: "::totally::valid::but::extra::"]\n',
+        '[(cpp) namespace: ":::extra::colon"]\n',
+        '[(cpp) namespace: "::extra:::colon"]\n',
+    ]:
+      ir = _make_ir_from_emb(test)
+      attr = ir.module[0].attribute[0]
+      self.assertEqual([[
+          error.error("m.emb", attr.value.source_location,
+                      'Invalid namespace, must be a valid C++ namespace, such '
+                      'as "abc", "abc::def", or "::abc::def::ghi" (ISO/IEC '
+                      '14882:2017 enclosing-namespace-specifier).')
+      ]], header_generator.generate_header(ir)[1])
+
+  def test_rejects_empty_namespace(self):
+    for test in [
+        '[(cpp) namespace: ""]\n',
+        '[(cpp) namespace: " "]\n',
+        '[(cpp) namespace: "    "]\n',
+    ]:
+      ir = _make_ir_from_emb(test)
+      attr = ir.module[0].attribute[0]
+      self.assertEqual([[
+          error.error("m.emb", attr.value.source_location,
+                      'Empty namespace value is not allowed.')
+      ]], header_generator.generate_header(ir)[1])
+
+  def test_rejects_global_namespace(self):
+    for test in [
+        '[(cpp) namespace: "::"]\n',
+        '[(cpp) namespace: "  ::"]\n',
+        '[(cpp) namespace: ":: "]\n',
+        '[(cpp) namespace: " ::  "]\n',
+    ]:
+      ir = _make_ir_from_emb(test)
+      attr = ir.module[0].attribute[0]
+      self.assertEqual([[
+          error.error("m.emb", attr.value.source_location,
+                      'Global namespace is not allowed.')
+      ]], header_generator.generate_header(ir)[1])
+
+  def test_rejects_reserved_namespace(self):
+    for test, expected in [
+        # Only component
+        ('[(cpp) namespace: "class"]\n', 'class'),
+        # Only component, fully qualified name
+        ('[(cpp) namespace: "::const"]\n', 'const'),
+        # First component
+        ('[(cpp) namespace: "if::valid"]\n', 'if'),
+        # First component, fully qualified name
+        ('[(cpp) namespace: "::auto::pilot"]\n', 'auto'),
+        # Last component
+        ('[(cpp) namespace: "make::do"]\n', 'do'),
+        # Middle component
+        ('[(cpp) namespace: "our::new::product"]\n', 'new'),
+    ]:
+      ir = _make_ir_from_emb(test)
+      attr = ir.module[0].attribute[0]
+
+      self.assertEqual([[
+          error.error("m.emb", attr.value.source_location,
+                      f'Reserved word "{expected}" is not allowed '
+                      f'as a namespace component.')]],
+          header_generator.generate_header(ir)[1])
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/compiler/back_end/cpp/testcode/enum_case_test.cc b/compiler/back_end/cpp/testcode/enum_case_test.cc
new file mode 100644
index 0000000..247ad9d
--- /dev/null
+++ b/compiler/back_end/cpp/testcode/enum_case_test.cc
@@ -0,0 +1,303 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     https://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.
+
+// Tests the `enum_case` attribute generating the correct case. Note that since
+// these tests are regarding the name of enum members, it is likely that if this
+// test would fail, it may fail to compile.
+
+#include "gtest/gtest.h"
+#include "testdata/enum_case.emb.h"
+
+namespace emboss {
+namespace test {
+namespace {
+
+TEST(EnumShouty, AccessValuesByNameInSource) {
+  EXPECT_EQ(static_cast<int>(EnumShouty::FIRST), 0);
+  EXPECT_EQ(static_cast<int>(EnumShouty::SECOND), 1);
+  EXPECT_EQ(static_cast<int>(EnumShouty::TWO_WORD), 2);
+  EXPECT_EQ(static_cast<int>(EnumShouty::THREE_WORD_ENUM), 4);
+  EXPECT_EQ(static_cast<int>(EnumShouty::LONG_ENUM_VALUE_NAME), 8);
+}
+
+TEST(EnumShouty, EnumIsKnown) {
+  EXPECT_TRUE(EnumIsKnown(EnumShouty::FIRST));
+  EXPECT_TRUE(EnumIsKnown(EnumShouty::SECOND));
+  EXPECT_TRUE(EnumIsKnown(EnumShouty::TWO_WORD));
+  EXPECT_TRUE(EnumIsKnown(EnumShouty::THREE_WORD_ENUM));
+  EXPECT_TRUE(EnumIsKnown(EnumShouty::LONG_ENUM_VALUE_NAME));
+  EXPECT_FALSE(EnumIsKnown(static_cast<EnumShouty>(999)));
+}
+
+TEST(EnumShouty, NameToEnum) {
+  EnumShouty result;
+
+  EXPECT_TRUE(TryToGetEnumFromName("FIRST", &result));
+  EXPECT_EQ(EnumShouty::FIRST, result);
+  EXPECT_TRUE(TryToGetEnumFromName("SECOND", &result));
+  EXPECT_EQ(EnumShouty::SECOND, result);
+  EXPECT_TRUE(TryToGetEnumFromName("TWO_WORD", &result));
+  EXPECT_EQ(EnumShouty::TWO_WORD, result);
+  EXPECT_TRUE(TryToGetEnumFromName("THREE_WORD_ENUM", &result));
+  EXPECT_EQ(EnumShouty::THREE_WORD_ENUM, result);
+  EXPECT_TRUE(TryToGetEnumFromName("LONG_ENUM_VALUE_NAME", &result));
+  EXPECT_EQ(EnumShouty::LONG_ENUM_VALUE_NAME, result);
+}
+
+TEST(EnumShouty, NameToEnumFailsWithKCamel) {
+  EnumShouty result = EnumShouty::FIRST;
+
+  EXPECT_FALSE(TryToGetEnumFromName("kSecond", &result));
+  EXPECT_EQ(EnumShouty::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kTwoWord", &result));
+  EXPECT_EQ(EnumShouty::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kThreeWordEnum", &result));
+  EXPECT_EQ(EnumShouty::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kLongEnumValueName", &result));
+  EXPECT_EQ(EnumShouty::FIRST, result);
+}
+
+TEST(EnumShouty, EnumToName) {
+  EXPECT_EQ("FIRST", TryToGetNameFromEnum(EnumShouty::FIRST));
+  EXPECT_EQ("SECOND", TryToGetNameFromEnum(EnumShouty::SECOND));
+  EXPECT_EQ("TWO_WORD", TryToGetNameFromEnum(EnumShouty::TWO_WORD));
+  EXPECT_EQ("THREE_WORD_ENUM",
+    TryToGetNameFromEnum(EnumShouty::THREE_WORD_ENUM));
+  EXPECT_EQ("LONG_ENUM_VALUE_NAME",
+    TryToGetNameFromEnum(EnumShouty::LONG_ENUM_VALUE_NAME));
+}
+
+TEST(EnumDefault, AccessValuesByNameInSource) {
+  EXPECT_EQ(static_cast<int>(EnumDefault::kFirst), 0);
+  EXPECT_EQ(static_cast<int>(EnumDefault::kSecond), 1);
+  EXPECT_EQ(static_cast<int>(EnumDefault::kTwoWord), 2);
+  EXPECT_EQ(static_cast<int>(EnumDefault::kThreeWordEnum), 4);
+  EXPECT_EQ(static_cast<int>(EnumDefault::kLongEnumValueName), 8);
+}
+
+TEST(EnumDefault, EnumIsKnown) {
+  EXPECT_TRUE(EnumIsKnown(EnumDefault::kFirst));
+  EXPECT_TRUE(EnumIsKnown(EnumDefault::kSecond));
+  EXPECT_TRUE(EnumIsKnown(EnumDefault::kTwoWord));
+  EXPECT_TRUE(EnumIsKnown(EnumDefault::kThreeWordEnum));
+  EXPECT_TRUE(EnumIsKnown(EnumDefault::kLongEnumValueName));
+  EXPECT_FALSE(EnumIsKnown(static_cast<EnumDefault>(999)));
+}
+
+TEST(EnumDefault, NameToEnum) {
+  EnumDefault result;
+
+  EXPECT_TRUE(TryToGetEnumFromName("FIRST", &result));
+  EXPECT_EQ(EnumDefault::kFirst, result);
+  EXPECT_TRUE(TryToGetEnumFromName("SECOND", &result));
+  EXPECT_EQ(EnumDefault::kSecond, result);
+  EXPECT_TRUE(TryToGetEnumFromName("TWO_WORD", &result));
+  EXPECT_EQ(EnumDefault::kTwoWord, result);
+  EXPECT_TRUE(TryToGetEnumFromName("THREE_WORD_ENUM", &result));
+  EXPECT_EQ(EnumDefault::kThreeWordEnum, result);
+  EXPECT_TRUE(TryToGetEnumFromName("LONG_ENUM_VALUE_NAME", &result));
+  EXPECT_EQ(EnumDefault::kLongEnumValueName, result);
+}
+
+TEST(EnumDefault, NameToEnumFailsWithKCamel) {
+  EnumDefault result = EnumDefault::kFirst;
+
+  EXPECT_FALSE(TryToGetEnumFromName("kFirst", &result));
+  EXPECT_EQ(EnumDefault::kFirst, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kSecond", &result));
+  EXPECT_EQ(EnumDefault::kFirst, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kTwoWord", &result));
+  EXPECT_EQ(EnumDefault::kFirst, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kThreeWordEnum", &result));
+  EXPECT_EQ(EnumDefault::kFirst, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kLongEnumValueName", &result));
+  EXPECT_EQ(EnumDefault::kFirst, result);
+}
+
+TEST(EnumDefault, EnumToName) {
+  EXPECT_EQ("FIRST", TryToGetNameFromEnum(EnumDefault::kFirst));
+  EXPECT_EQ("SECOND", TryToGetNameFromEnum(EnumDefault::kSecond));
+  EXPECT_EQ("TWO_WORD", TryToGetNameFromEnum(EnumDefault::kTwoWord));
+  EXPECT_EQ("THREE_WORD_ENUM",
+    TryToGetNameFromEnum(EnumDefault::kThreeWordEnum));
+  EXPECT_EQ("LONG_ENUM_VALUE_NAME",
+    TryToGetNameFromEnum(EnumDefault::kLongEnumValueName));
+}
+
+TEST(EnumShoutyAndKCamel, AccessValuesByNameInSource) {
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::FIRST), 0);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::kFirst), 0);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::SECOND), 1);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::kSecond), 1);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::TWO_WORD), 2);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::kTwoWord), 2);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::THREE_WORD_ENUM), 4);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::kThreeWordEnum), 4);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::LONG_ENUM_VALUE_NAME), 8);
+  EXPECT_EQ(static_cast<int>(EnumShoutyAndKCamel::kLongEnumValueName), 8);
+}
+
+TEST(EnumShoutyAndKCamel, EnumIsKnown) {
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::FIRST));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::SECOND));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::TWO_WORD));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::THREE_WORD_ENUM));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::LONG_ENUM_VALUE_NAME));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::kFirst));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::kSecond));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::kTwoWord));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::kThreeWordEnum));
+  EXPECT_TRUE(EnumIsKnown(EnumShoutyAndKCamel::kLongEnumValueName));
+  EXPECT_FALSE(EnumIsKnown(static_cast<EnumShoutyAndKCamel>(999)));
+}
+
+TEST(EnumShoutyAndKCamel, NameToEnum) {
+  EnumShoutyAndKCamel result;
+
+  EXPECT_TRUE(TryToGetEnumFromName("FIRST", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_TRUE(TryToGetEnumFromName("SECOND", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::SECOND, result);
+  EXPECT_TRUE(TryToGetEnumFromName("TWO_WORD", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::TWO_WORD, result);
+  EXPECT_TRUE(TryToGetEnumFromName("THREE_WORD_ENUM", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::THREE_WORD_ENUM, result);
+  EXPECT_TRUE(TryToGetEnumFromName("LONG_ENUM_VALUE_NAME", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::LONG_ENUM_VALUE_NAME, result);
+}
+
+TEST(EnumShoutyAndKCamel, NameToEnumFailsWithKCamel) {
+  EnumShoutyAndKCamel result = EnumShoutyAndKCamel::FIRST;
+
+  EXPECT_FALSE(TryToGetEnumFromName("kFirst", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kSecond", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kTwoWord", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kThreeWordEnum", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kLongEnumValueName", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+}
+
+TEST(EnumShoutyAndKCamel, EnumToName) {
+  EXPECT_EQ("FIRST", TryToGetNameFromEnum(EnumShoutyAndKCamel::FIRST));
+  EXPECT_EQ("SECOND", TryToGetNameFromEnum(EnumShoutyAndKCamel::SECOND));
+  EXPECT_EQ("TWO_WORD", TryToGetNameFromEnum(EnumShoutyAndKCamel::TWO_WORD));
+  EXPECT_EQ("THREE_WORD_ENUM",
+    TryToGetNameFromEnum(EnumShoutyAndKCamel::THREE_WORD_ENUM));
+  EXPECT_EQ("LONG_ENUM_VALUE_NAME",
+    TryToGetNameFromEnum(EnumShoutyAndKCamel::LONG_ENUM_VALUE_NAME));
+
+  EXPECT_EQ("FIRST", TryToGetNameFromEnum(EnumShoutyAndKCamel::kFirst));
+  EXPECT_EQ("SECOND", TryToGetNameFromEnum(EnumShoutyAndKCamel::kSecond));
+  EXPECT_EQ("TWO_WORD", TryToGetNameFromEnum(EnumShoutyAndKCamel::kTwoWord));
+  EXPECT_EQ("THREE_WORD_ENUM",
+    TryToGetNameFromEnum(EnumShoutyAndKCamel::kThreeWordEnum));
+  EXPECT_EQ("LONG_ENUM_VALUE_NAME",
+    TryToGetNameFromEnum(EnumShoutyAndKCamel::kLongEnumValueName));
+}
+
+TEST(EnumMixed, AccessValuesByNameInSource) {
+  EXPECT_EQ(static_cast<int>(EnumMixed::FIRST), 0);
+  EXPECT_EQ(static_cast<int>(EnumMixed::kFirst), 0);
+  EXPECT_EQ(static_cast<int>(EnumMixed::SECOND), 1);
+  EXPECT_EQ(static_cast<int>(EnumMixed::kTwoWord), 2);
+  EXPECT_EQ(static_cast<int>(EnumMixed::THREE_WORD_ENUM), 4);
+  EXPECT_EQ(static_cast<int>(EnumMixed::kThreeWordEnum), 4);
+  EXPECT_EQ(static_cast<int>(EnumMixed::kLongEnumValueName), 8);
+}
+
+TEST(EnumMixed, EnumIsKnown) {
+  EXPECT_TRUE(EnumIsKnown(EnumMixed::FIRST));
+  EXPECT_TRUE(EnumIsKnown(EnumMixed::SECOND));
+  EXPECT_TRUE(EnumIsKnown(EnumMixed::THREE_WORD_ENUM));
+  EXPECT_TRUE(EnumIsKnown(EnumMixed::kFirst));
+  EXPECT_TRUE(EnumIsKnown(EnumMixed::kTwoWord));
+  EXPECT_TRUE(EnumIsKnown(EnumMixed::kThreeWordEnum));
+  EXPECT_TRUE(EnumIsKnown(EnumMixed::kLongEnumValueName));
+  EXPECT_FALSE(EnumIsKnown(static_cast<EnumMixed>(999)));
+}
+
+TEST(EnumMixed, NameToEnum) {
+  EnumMixed result;
+
+  EXPECT_TRUE(TryToGetEnumFromName("FIRST", &result));
+  EXPECT_EQ(EnumMixed::FIRST, result);
+  EXPECT_TRUE(TryToGetEnumFromName("SECOND", &result));
+  EXPECT_EQ(EnumMixed::SECOND, result);
+  EXPECT_TRUE(TryToGetEnumFromName("TWO_WORD", &result));
+  EXPECT_EQ(EnumMixed::kTwoWord, result);
+  EXPECT_TRUE(TryToGetEnumFromName("THREE_WORD_ENUM", &result));
+  EXPECT_EQ(EnumMixed::THREE_WORD_ENUM, result);
+  EXPECT_TRUE(TryToGetEnumFromName("LONG_ENUM_VALUE_NAME", &result));
+  EXPECT_EQ(EnumMixed::kLongEnumValueName, result);
+}
+
+TEST(EnumMixed, NameToEnumFailsWithKCamel) {
+  EnumShoutyAndKCamel result = EnumShoutyAndKCamel::FIRST;
+
+  EXPECT_FALSE(TryToGetEnumFromName("kFirst", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kSecond", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kTwoWord", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kThreeWordEnum", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+  EXPECT_FALSE(TryToGetEnumFromName("kLongEnumValueName", &result));
+  EXPECT_EQ(EnumShoutyAndKCamel::FIRST, result);
+}
+
+TEST(EnumMixed, EnumToName) {
+  EXPECT_EQ("FIRST", TryToGetNameFromEnum(EnumMixed::FIRST));
+  EXPECT_EQ("FIRST", TryToGetNameFromEnum(EnumMixed::kFirst));
+  EXPECT_EQ("SECOND", TryToGetNameFromEnum(EnumMixed::SECOND));
+  EXPECT_EQ("TWO_WORD", TryToGetNameFromEnum(EnumMixed::kTwoWord));
+  EXPECT_EQ("THREE_WORD_ENUM",
+    TryToGetNameFromEnum(EnumMixed::THREE_WORD_ENUM));
+  EXPECT_EQ("THREE_WORD_ENUM",
+    TryToGetNameFromEnum(EnumMixed::kThreeWordEnum));
+  EXPECT_EQ("LONG_ENUM_VALUE_NAME",
+    TryToGetNameFromEnum(EnumMixed::kLongEnumValueName));
+}
+
+TEST(UseKCamelEnumCase, IsValidToUse) {
+  std::array<uint8_t, UseKCamelEnumCase::IntrinsicSizeInBytes()> buffer;
+  auto view = MakeUseKCamelEnumCaseView(&buffer);
+
+  EXPECT_EQ(UseKCamelEnumCase::first(), EnumDefault::kFirst);
+  EXPECT_EQ(view.first().Read(), EnumDefault::kFirst);
+
+  EXPECT_TRUE(view.v().TryToWrite(EnumDefault::kSecond));
+  EXPECT_FALSE(view.v_is_first().Read());
+
+  EXPECT_TRUE(view.v().TryToWrite(EnumDefault::kFirst));
+  EXPECT_TRUE(view.v_is_first().Read());
+}
+
+TEST(UseKCamelEnumCase, TextStream) {
+  std::array<uint8_t, UseKCamelEnumCase::IntrinsicSizeInBytes()> buffer;
+  auto view = MakeUseKCamelEnumCaseView(&buffer);
+
+  EXPECT_TRUE(view.v().TryToWrite(EnumDefault::kSecond));
+  EXPECT_EQ(WriteToString(view), "{ v: SECOND }");
+  EXPECT_TRUE(UpdateFromText(view, "{ v: TWO_WORD }"));
+  EXPECT_EQ(view.v().Read(), EnumDefault::kTwoWord);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace emboss
diff --git a/compiler/front_end/attribute_checker.py b/compiler/front_end/attribute_checker.py
index 21c6b22..cb8db74 100644
--- a/compiler/front_end/attribute_checker.py
+++ b/compiler/front_end/attribute_checker.py
@@ -397,17 +397,6 @@
     ])
 
 
-def _gather_default_attributes(obj, defaults):
-  defaults = defaults.copy()
-  for attr in obj.attribute:
-    if attr.is_default:
-      defaulted_attr = ir_pb2.Attribute()
-      defaulted_attr.CopyFrom(attr)
-      defaulted_attr.is_default = False
-      defaults[attr.name.text] = defaulted_attr
-  return {"defaults": defaults}
-
-
 def _add_missing_attributes_on_ir(ir):
   """Adds missing attributes in a complete IR."""
   traverse_ir.fast_traverse_ir_top_down(
@@ -419,17 +408,17 @@
   traverse_ir.fast_traverse_ir_top_down(
       ir, [ir_pb2.Structure], _add_missing_size_attributes_on_structure,
       incidental_actions={
-          ir_pb2.Module: _gather_default_attributes,
-          ir_pb2.TypeDefinition: _gather_default_attributes,
-          ir_pb2.Field: _gather_default_attributes,
+          ir_pb2.Module: attribute_util.gather_default_attributes,
+          ir_pb2.TypeDefinition: attribute_util.gather_default_attributes,
+          ir_pb2.Field: attribute_util.gather_default_attributes,
       },
       parameters={"defaults": {}})
   traverse_ir.fast_traverse_ir_top_down(
       ir, [ir_pb2.Field], _add_missing_byte_order_attribute_on_field,
       incidental_actions={
-          ir_pb2.Module: _gather_default_attributes,
-          ir_pb2.TypeDefinition: _gather_default_attributes,
-          ir_pb2.Field: _gather_default_attributes,
+          ir_pb2.Module: attribute_util.gather_default_attributes,
+          ir_pb2.TypeDefinition: attribute_util.gather_default_attributes,
+          ir_pb2.Field: attribute_util.gather_default_attributes,
       },
       parameters={"defaults": {}})
   return []
diff --git a/compiler/front_end/emboss_front_end.py b/compiler/front_end/emboss_front_end.py
index 6388128..7df6782 100644
--- a/compiler/front_end/emboss_front_end.py
+++ b/compiler/front_end/emboss_front_end.py
@@ -89,8 +89,9 @@
 def _show_errors(errors, ir, flags):
   """Prints errors with source code snippets."""
   source_codes = {}
-  for module in ir.module:
-    source_codes[module.source_file_name] = module.source_text
+  if ir:
+    for module in ir.module:
+      source_codes[module.source_file_name] = module.source_text
   use_color = (flags.color_output == "always" or
                (flags.color_output in ("auto", "if_tty") and
                 os.isatty(sys.stderr.fileno())))
@@ -132,7 +133,7 @@
   ir, debug_info, errors = glue.parse_emboss_file(
       flags.input_file[0], _find_in_dirs_and_read(flags.import_dirs))
   if errors:
-    _show_errors(errors, flags)
+    _show_errors(errors, ir, flags)
     return 1
   main_module_debug_info = debug_info.modules[flags.input_file[0]]
   if flags.debug_show_tokenization:
diff --git a/compiler/util/attribute_util.py b/compiler/util/attribute_util.py
index 8d75de6..d864364 100644
--- a/compiler/util/attribute_util.py
+++ b/compiler/util/attribute_util.py
@@ -284,3 +284,26 @@
     else:
       errors.extend(types[attr.name.text](attr, module_source_file))
   return errors
+
+
+def gather_default_attributes(obj, defaults):
+  """Gathers default attributes for an IR object
+
+  This is designed to be able to be used as-is as an incidental action in an IR
+  traversal to accumulate defaults for child nodes.
+
+  Arguments:
+    defaults: A dict of `{ "defaults": { attr.name.text: attr } }`
+
+  Returns:
+    A dict of `{ "defaults": { attr.name.text: attr } }` with any defaults
+    provided by `obj` added/overridden.
+  """
+  defaults = defaults.copy()
+  for attr in obj.attribute:
+    if attr.is_default:
+      defaulted_attr = ir_pb2.Attribute()
+      defaulted_attr.CopyFrom(attr)
+      defaulted_attr.is_default = False
+      defaults[attr.name.text] = defaulted_attr
+  return {"defaults": defaults}
diff --git a/compiler/util/name_conversion.py b/compiler/util/name_conversion.py
index 71aeff5..3f32809 100644
--- a/compiler/util/name_conversion.py
+++ b/compiler/util/name_conversion.py
@@ -14,6 +14,58 @@
 
 """Conversions between snake-, camel-, and shouty-case names."""
 
+from enum import Enum
 
+
+class Case(str, Enum):
+  SNAKE = "snake_case"
+  SHOUTY = "SHOUTY_CASE"
+  CAMEL = "CamelCase"
+  K_CAMEL = "kCamelCase"
+
+
+# Map of (from, to) cases to their conversion function. Initially only contains
+# identity case conversions, additional conversions are added with the
+# _case_conversion decorator.
+_case_conversions = {(case.value, case.value): lambda x: x for case in Case}
+
+
+def _case_conversion(case_from, case_to):
+  """Decorator to dynamically dispatch case conversions at runtime."""
+  def _func(f):
+    _case_conversions[case_from, case_to] = f
+    return f
+
+  return _func
+
+
+@_case_conversion(Case.SNAKE, Case.CAMEL)
+@_case_conversion(Case.SHOUTY, Case.CAMEL)
 def snake_to_camel(name):
+  """Convert from snake_case to CamelCase. Also works from SHOUTY_CASE."""
   return "".join(word.capitalize() for word in name.split("_"))
+
+
+@_case_conversion(Case.CAMEL, Case.K_CAMEL)
+def camel_to_k_camel(name):
+  """Convert from CamelCase to kCamelCase."""
+  return "k" + name
+
+
+@_case_conversion(Case.SNAKE, Case.K_CAMEL)
+@_case_conversion(Case.SHOUTY, Case.K_CAMEL)
+def snake_to_k_camel(name):
+  """Converts from snake_case to kCamelCase. Also works from SHOUTY_CASE."""
+  return camel_to_k_camel(snake_to_camel(name))
+
+
+def convert_case(case_from, case_to, value):
+  """Converts cases based on runtime case values.
+
+  Note: Cases can be strings or enum values."""
+  return _case_conversions[case_from, case_to](value)
+
+
+def is_case_conversion_supported(case_from, case_to):
+  """Determines if a case conversion would be supported"""
+  return (case_from, case_to) in _case_conversions
diff --git a/compiler/util/name_conversion_test.py b/compiler/util/name_conversion_test.py
index a980a1d..9e41ecc 100644
--- a/compiler/util/name_conversion_test.py
+++ b/compiler/util/name_conversion_test.py
@@ -29,6 +29,61 @@
     self.assertEqual("Abc89Def", name_conversion.snake_to_camel("abc_89_def"))
     self.assertEqual("Abc89def", name_conversion.snake_to_camel("abc_89def"))
 
+  def test_shouty_to_camel(self):
+    self.assertEqual("Abc", name_conversion.snake_to_camel("ABC"))
+    self.assertEqual("AbcDef", name_conversion.snake_to_camel("ABC_DEF"))
+    self.assertEqual("AbcDef89", name_conversion.snake_to_camel("ABC_DEF89"))
+    self.assertEqual("AbcDef89", name_conversion.snake_to_camel("ABC_DEF_89"))
+    self.assertEqual("Abc89Def", name_conversion.snake_to_camel("ABC_89_DEF"))
+    self.assertEqual("Abc89def", name_conversion.snake_to_camel("ABC_89DEF"))
+
+  def test_camel_to_k_camel(self):
+    self.assertEqual("kFoo", name_conversion.camel_to_k_camel("Foo"))
+    self.assertEqual("kFooBar", name_conversion.camel_to_k_camel("FooBar"))
+    self.assertEqual("kAbc123", name_conversion.camel_to_k_camel("Abc123"))
+
+  def test_snake_to_k_camel(self):
+    self.assertEqual("kAbc", name_conversion.snake_to_k_camel("abc"))
+    self.assertEqual("kAbcDef", name_conversion.snake_to_k_camel("abc_def"))
+    self.assertEqual("kAbcDef89",
+                     name_conversion.snake_to_k_camel("abc_def89"))
+    self.assertEqual("kAbcDef89",
+                     name_conversion.snake_to_k_camel("abc_def_89"))
+    self.assertEqual("kAbc89Def",
+                     name_conversion.snake_to_k_camel("abc_89_def"))
+    self.assertEqual("kAbc89def",
+                     name_conversion.snake_to_k_camel("abc_89def"))
+
+  def test_shouty_to_k_camel(self):
+    self.assertEqual("kAbc", name_conversion.snake_to_k_camel("ABC"))
+    self.assertEqual("kAbcDef", name_conversion.snake_to_k_camel("ABC_DEF"))
+    self.assertEqual("kAbcDef89",
+                     name_conversion.snake_to_k_camel("ABC_DEF89"))
+    self.assertEqual("kAbcDef89",
+                     name_conversion.snake_to_k_camel("ABC_DEF_89"))
+    self.assertEqual("kAbc89Def",
+                     name_conversion.snake_to_k_camel("ABC_89_DEF"))
+    self.assertEqual("kAbc89def",
+                     name_conversion.snake_to_k_camel("ABC_89DEF"))
+
+  def test_convert_case(self):
+    self.assertEqual("foo_bar_123", name_conversion.convert_case(
+      "snake_case", "snake_case", "foo_bar_123"))
+    self.assertEqual("FOO_BAR_123", name_conversion.convert_case(
+      "SHOUTY_CASE", "SHOUTY_CASE", "FOO_BAR_123"))
+    self.assertEqual("kFooBar123", name_conversion.convert_case(
+      "kCamelCase", "kCamelCase", "kFooBar123"))
+    self.assertEqual("FooBar123", name_conversion.convert_case(
+      "CamelCase", "CamelCase", "FooBar123"))
+    self.assertEqual("kAbcDef", name_conversion.convert_case(
+      "snake_case", "kCamelCase", "abc_def"))
+    self.assertEqual("AbcDef", name_conversion.convert_case(
+      "snake_case", "CamelCase", "abc_def"))
+    self.assertEqual("kAbcDef", name_conversion.convert_case(
+      "SHOUTY_CASE", "kCamelCase", "ABC_DEF"))
+    self.assertEqual("AbcDef", name_conversion.convert_case(
+      "SHOUTY_CASE", "CamelCase", "ABC_DEF"))
+
 
 if __name__ == "__main__":
   unittest.main()
diff --git a/doc/design_docs/alternate_enum_cases.md b/doc/design_docs/archive/alternate_enum_cases.md
similarity index 98%
rename from doc/design_docs/alternate_enum_cases.md
rename to doc/design_docs/archive/alternate_enum_cases.md
index 846db26..eb2c76c 100644
--- a/doc/design_docs/alternate_enum_cases.md
+++ b/doc/design_docs/archive/alternate_enum_cases.md
@@ -1,5 +1,9 @@
 # Design: Alternate Enum Field Cases
 
+This document is provided for historical interest.  This feature is now
+implemented in the form of the `[enum_case]` attribute on `enum` values, which
+can also be `$default`ed on module, struct, bits, and enum definitions.
+
 ## Motivation
 
 Currently, the Emboss compiler requires that enum fields are `SHOUTY_CASE`, but
diff --git a/doc/language-reference.md b/doc/language-reference.md
index 7eda923..40da052 100644
--- a/doc/language-reference.md
+++ b/doc/language-reference.md
@@ -222,6 +222,33 @@
 The `namespace` attribute may only be used at the module level; all structures
 and enums within a module will be placed in the same namespace.
 
+### `(cpp) enum_case`
+
+The `enum_case` attribute can be specified for the C++ backend to specify
+in which case the enum values should be emitted to generated source. It does
+not change the text representation, which always uses the original emboss
+definition name as the canonical name.
+
+Currently, the supported cases are`SHOUTY_CASE` and `kCamelCase`.
+
+A `$default` enum case can be set on a module, struct, bits, or enum and
+applies to all enum values within that module, struct, bits, or enum
+definition.
+
+For example, to use `kCamelCase` by default for all enum values in a module:
+
+```
+[$default enum_case: "kCamelCase"]
+```
+
+This will change enum names like `UPPER_CHANNEL_RANGE_LIMIT` to
+`kUpperChannelRangeLimit` in the C++ source for all enum values in the module.
+Multiple case names can be specified, which is especially useful when
+transitioning between two cases:
+
+```
+[enum_case: "SHOUTY_CASE, kCamelCase"]
+```
 
 ### `text_output`
 
diff --git a/testdata/BUILD b/testdata/BUILD
index 083298c..8ee7327 100644
--- a/testdata/BUILD
+++ b/testdata/BUILD
@@ -47,6 +47,7 @@
         "cpp_namespace.emb",
         "dynamic_size.emb",
         "enum.emb",
+        "enum_case.emb",
         "explicit_sizes.emb",
         "float.emb",
         "imported.emb",
@@ -107,6 +108,13 @@
 )
 
 emboss_cc_library(
+    name = "enum_case_emboss",
+    srcs = [
+        "enum_case.emb",
+    ],
+)
+
+emboss_cc_library(
     name = "explicit_sizes_emboss",
     srcs = [
         "explicit_sizes.emb",
diff --git a/testdata/enum_case.emb b/testdata/enum_case.emb
new file mode 100644
index 0000000..04f3bfe
--- /dev/null
+++ b/testdata/enum_case.emb
@@ -0,0 +1,55 @@
+# Copyright 2023 Google LLC
+#
+# 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
+#
+#     https://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.
+
+[$default byte_order: "LittleEndian"]
+[(cpp) namespace: "emboss::test"]
+[(cpp) $default enum_case: "kCamelCase"]
+
+enum EnumShouty:
+  [(cpp) $default enum_case: "SHOUTY_CASE"]
+  FIRST                = 0
+  SECOND               = 1
+  TWO_WORD             = 2
+  THREE_WORD_ENUM      = 4
+  LONG_ENUM_VALUE_NAME = 8
+
+enum EnumDefault:
+  FIRST                = 0
+  SECOND               = 1
+  TWO_WORD             = 2
+  THREE_WORD_ENUM      = 4
+  LONG_ENUM_VALUE_NAME = 8
+
+struct UseKCamelEnumCase:
+  0 [+4] EnumDefault v
+  let first = EnumDefault.FIRST
+  let v_is_first = v == EnumDefault.FIRST
+
+enum EnumShoutyAndKCamel:
+  [(cpp) $default enum_case: "SHOUTY_CASE, kCamelCase"]
+  FIRST                = 0
+  SECOND               = 1
+  TWO_WORD             = 2
+  THREE_WORD_ENUM      = 4
+  LONG_ENUM_VALUE_NAME = 8
+
+enum EnumMixed:
+  -- Tests mixing various `enum_case` values in the same enum definition.
+  FIRST                = 0  [(cpp) enum_case: "SHOUTY_CASE, kCamelCase"]
+  SECOND               = 1  [(cpp) enum_case: "SHOUTY_CASE"]
+  TWO_WORD             = 2
+      [(cpp) enum_case: "kCamelCase"]
+  THREE_WORD_ENUM      = 4
+      [(cpp) enum_case: "kCamelCase, SHOUTY_CASE"]
+  LONG_ENUM_VALUE_NAME = 8