Merge pull request #134 from EricRahm/enum_trait

Split out enum trait generation
diff --git a/build_defs.bzl b/build_defs.bzl
index 14e6045..0c1e6a8 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -26,7 +26,7 @@
 
 load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
 
-def emboss_cc_library(name, srcs, deps = [], visibility = None, import_dirs = []):
+def emboss_cc_library(name, srcs, deps = [], visibility = None, import_dirs = [], enable_enum_traits = True):
     """Constructs a C++ library from an .emb file."""
     if len(srcs) != 1:
         fail(
@@ -45,6 +45,7 @@
         name = name,
         deps = [":" + name + "_ir"],
         visibility = visibility,
+        enable_enum_traits = enable_enum_traits,
     )
 
 # Full Starlark rules for emboss_library and cc_emboss_library.
@@ -174,6 +175,8 @@
     args.add_all(emboss_info.direct_ir)
     args.add("--output-file")
     args.add_all(headers)
+    if not ctx.attr.enable_enum_traits:
+      args.add("--no-cc-enum-traits")
     ctx.actions.run(
         executable = emboss_cc_compiler,
         arguments = [args],
@@ -222,6 +225,9 @@
         "_emboss_cc_runtime": attr.label(
             default = "@com_google_emboss//runtime/cpp:cpp_utils",
         ),
+        "enable_enum_traits": attr.bool(
+            default = True,
+        ),
     },
     toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
 )
@@ -244,6 +250,9 @@
             allow_rules = ["emboss_library"],
             allow_files = False,
         ),
+        "enable_enum_traits": attr.bool(
+            default = True,
+        ),
     },
     provides = [CcInfo, EmbossInfo],
 )
diff --git a/compiler/back_end/cpp/BUILD b/compiler/back_end/cpp/BUILD
index e9ae268..1c496d6 100644
--- a/compiler/back_end/cpp/BUILD
+++ b/compiler/back_end/cpp/BUILD
@@ -225,6 +225,17 @@
 )
 
 emboss_cc_test(
+    name = "no_enum_traits_test",
+    srcs = [
+        "testcode/no_enum_traits_test.cc",
+    ],
+    deps = [
+        "//testdata:no_enum_traits_emboss",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_test(
     name = "start_size_range_test",
     srcs = [
         "testcode/start_size_range_test.cc",
diff --git a/compiler/back_end/cpp/emboss_codegen_cpp.py b/compiler/back_end/cpp/emboss_codegen_cpp.py
index ed5e66e..4ac71f0 100644
--- a/compiler/back_end/cpp/emboss_codegen_cpp.py
+++ b/compiler/back_end/cpp/emboss_codegen_cpp.py
@@ -45,6 +45,11 @@
                       choices=["always", "never", "if_tty", "auto"],
                       help="Print error messages using color.  'auto' is a "
                            "synonym for 'if_tty'.")
+  parser.add_argument("--cc-enum-traits",
+                      action=argparse.BooleanOptionalAction,
+                      default=True,
+                      help="""Controls generation of EnumTraits by the C++
+                              backend""")
   return parser.parse_args(argv[1:])
 
 
@@ -58,17 +63,18 @@
                 os.isatty(sys.stderr.fileno())))
   print(error.format_errors(errors, source_codes, use_color), file=sys.stderr)
 
-def generate_headers_and_log_errors(ir, color_output):
+def generate_headers_and_log_errors(ir, color_output, config: header_generator.Config):
   """Generates a C++ header and logs any errors.
 
   Arguments:
     ir: EmbossIr of the module.
     color_output: "always", "never", "if_tty", "auto"
+    config: Header generation configuration.
 
   Returns:
     A tuple of (header, errors)
   """
-  header, errors = header_generator.generate_header(ir)
+  header, errors = header_generator.generate_header(ir, config)
   if errors:
     _show_errors(errors, ir, color_output)
   return (header, errors)
@@ -79,7 +85,8 @@
       ir = ir_pb2.EmbossIr.from_json(f.read())
   else:
     ir = ir_pb2.EmbossIr.from_json(sys.stdin.read())
-  header, errors = generate_headers_and_log_errors(ir, flags.color_output)
+  config = header_generator.Config(include_enum_traits=flags.cc_enum_traits)
+  header, errors = generate_headers_and_log_errors(ir, flags.color_output, config)
   if errors:
     return 1
   if flags.output_file:
diff --git a/compiler/back_end/cpp/generated_code_templates b/compiler/back_end/cpp/generated_code_templates
index df9ecac..a0acc68 100644
--- a/compiler/back_end/cpp/generated_code_templates
+++ b/compiler/back_end/cpp/generated_code_templates
@@ -179,57 +179,7 @@
         emboss_reserved_local_other.IntrinsicSizeIn${units}().Read());
   }
 
-  template <class Stream>
-  bool UpdateFromTextStream(Stream *emboss_reserved_local_stream) const {
-    ::std::string emboss_reserved_local_brace;
-    if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
-                                      &emboss_reserved_local_brace))
-      return false;
-    if (emboss_reserved_local_brace != "{") return false;
-    for (;;) {
-      ::std::string emboss_reserved_local_name;
-      if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
-                                        &emboss_reserved_local_name))
-        return false;
-      if (emboss_reserved_local_name == ",")
-        if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
-                                          &emboss_reserved_local_name))
-          return false;
-      if (emboss_reserved_local_name == "}") return true;
-      ::std::string emboss_reserved_local_colon;
-      if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
-                                        &emboss_reserved_local_colon))
-        return false;
-      if (emboss_reserved_local_colon != ":") return false;
-${decode_fields}
-      // decode_fields will `continue` if it successfully finds a field.
-      return false;
-    }
-  }
-
-  template <class Stream>
-  void WriteToTextStream(
-      Stream *emboss_reserved_local_stream,
-      ::emboss::TextOutputOptions emboss_reserved_local_options) const {
-    ::emboss::TextOutputOptions emboss_reserved_local_field_options =
-        emboss_reserved_local_options.PlusOneIndent();
-    if (emboss_reserved_local_options.multiline()) {
-      emboss_reserved_local_stream->Write("{\n");
-    } else {
-      emboss_reserved_local_stream->Write("{");
-    }
-    bool emboss_reserved_local_wrote_field = false;
-${write_fields}
-    // Avoid unused variable warnings for empty structures:
-    (void)emboss_reserved_local_wrote_field;
-    if (emboss_reserved_local_options.multiline()) {
-      emboss_reserved_local_stream->Write(
-          emboss_reserved_local_options.current_indent());
-      emboss_reserved_local_stream->Write("}");
-    } else {
-      emboss_reserved_local_stream->Write(" }");
-    }
-  }
+${text_stream_methods}
 
   static constexpr bool IsAggregate() { return true; }
 
@@ -301,6 +251,60 @@
       emboss_reserved_local_size);
 }
 
+// ** struct_text_stream ** ////////////////////////////////////////////////////
+  template <class Stream>
+  bool UpdateFromTextStream(Stream *emboss_reserved_local_stream) const {
+    ::std::string emboss_reserved_local_brace;
+    if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
+                                      &emboss_reserved_local_brace))
+      return false;
+    if (emboss_reserved_local_brace != "{") return false;
+    for (;;) {
+      ::std::string emboss_reserved_local_name;
+      if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
+                                        &emboss_reserved_local_name))
+        return false;
+      if (emboss_reserved_local_name == ",")
+        if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
+                                          &emboss_reserved_local_name))
+          return false;
+      if (emboss_reserved_local_name == "}") return true;
+      ::std::string emboss_reserved_local_colon;
+      if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
+                                        &emboss_reserved_local_colon))
+        return false;
+      if (emboss_reserved_local_colon != ":") return false;
+${decode_fields}
+      // decode_fields will `continue` if it successfully finds a field.
+      return false;
+    }
+  }
+
+  template <class Stream>
+  void WriteToTextStream(
+      Stream *emboss_reserved_local_stream,
+      ::emboss::TextOutputOptions emboss_reserved_local_options) const {
+    ::emboss::TextOutputOptions emboss_reserved_local_field_options =
+        emboss_reserved_local_options.PlusOneIndent();
+    if (emboss_reserved_local_options.multiline()) {
+      emboss_reserved_local_stream->Write("{\n");
+    } else {
+      emboss_reserved_local_stream->Write("{");
+    }
+    bool emboss_reserved_local_wrote_field = false;
+${write_fields}
+    // Avoid unused variable warnings for empty structures:
+    (void)emboss_reserved_local_wrote_field;
+    if (emboss_reserved_local_options.multiline()) {
+      emboss_reserved_local_stream->Write(
+          emboss_reserved_local_options.current_indent());
+      emboss_reserved_local_stream->Write("}");
+    } else {
+      emboss_reserved_local_stream->Write(" }");
+    }
+  }
+
+
 // ** decode_field ** //////////////////////////////////////////////////////////
       // If the field name matches ${field_name}, handle it, otherwise fall
       // through to the next field.
@@ -772,6 +776,7 @@
 // "the output of this rule is an indeterminate number of files, all of which
 // should be used as input to this other rule," which would be necessary to
 // generate all the .cc files and then build and link them into a library.
+// ** enum_traits ** ///////////////////////////////////////////////////////////
 template <class Enum>
 class EnumTraits;
 
diff --git a/compiler/back_end/cpp/header_generator.py b/compiler/back_end/cpp/header_generator.py
index c671d6e..8b585ca 100644
--- a/compiler/back_end/cpp/header_generator.py
+++ b/compiler/back_end/cpp/header_generator.py
@@ -21,6 +21,7 @@
 import collections
 import pkgutil
 import re
+from typing import NamedTuple
 
 from compiler.back_end.cpp import attributes
 from compiler.back_end.util import code_template
@@ -77,6 +78,8 @@
 
 # TODO(bolms): This should be a command-line flag.
 _PRELUDE_INCLUDE_FILE = "runtime/cpp/emboss_prelude.h"
+_ENUM_VIEW_INCLUDE_FILE = "runtime/cpp/emboss_enum_view.h"
+_TEXT_UTIL_INCLUDE_FILE = "runtime/cpp/emboss_text_util.h"
 
 # Cases allowed in the `enum_case` attribute.
 _SUPPORTED_ENUM_CASES = ("SHOUTY_CASE", "kCamelCase")
@@ -86,6 +89,13 @@
   assert name_conversion.is_case_conversion_supported("SHOUTY_CASE", _enum_case)
 
 
+class Config(NamedTuple):
+  """Configuration for C++ header generation."""
+
+  include_enum_traits: bool = True
+  """Whether or not to include EnumTraits in the generated header."""
+
+
 def _get_namespace_components(namespace):
   """Gets the components of a C++ namespace
 
@@ -124,7 +134,7 @@
   return re.sub("['\"\\\\]", r"\\\0", string)
 
 
-def _get_includes(module):
+def _get_includes(module, config: Config):
   """Returns the appropriate #includes based on module's imports."""
   includes = []
   for import_ in module.foreign_import:
@@ -138,6 +148,13 @@
           code_template.format_template(
               _TEMPLATES.include,
               file_name=_cpp_string_escape(_PRELUDE_INCLUDE_FILE)))
+      if config.include_enum_traits:
+        includes.extend(
+          [code_template.format_template(
+              _TEMPLATES.include,
+              file_name=_cpp_string_escape(file_name))
+            for file_name in (_ENUM_VIEW_INCLUDE_FILE, _TEXT_UTIL_INCLUDE_FILE)
+          ])
   return "".join(includes)
 
 
@@ -1059,14 +1076,14 @@
   return "", declaration, definition
 
 
-def _generate_subtype_definitions(type_ir, ir):
+def _generate_subtype_definitions(type_ir, ir, config: Config):
   """Generates C++ code for subtypes of type_ir."""
   subtype_bodies = []
   subtype_forward_declarations = []
   subtype_method_definitions = []
   type_name = type_ir.name.name.text
   for subtype in type_ir.subtype:
-    inner_defs = _generate_type_definition(subtype, ir)
+    inner_defs = _generate_type_definition(subtype, ir, config)
     subtype_forward_declaration, subtype_body, subtype_methods = inner_defs
     subtype_forward_declarations.append(subtype_forward_declaration)
     subtype_bodies.append(subtype_body)
@@ -1096,7 +1113,7 @@
     return name
 
 
-def _generate_structure_definition(type_ir, ir):
+def _generate_structure_definition(type_ir, ir, config: Config):
   """Generates C++ for an Emboss structure (struct or bits).
 
   Arguments:
@@ -1108,7 +1125,7 @@
     suitable for insertion into the appropriate places in the generated header.
   """
   subtype_bodies, subtype_forward_declarations, subtype_method_definitions = (
-      _generate_subtype_definitions(type_ir, ir))
+      _generate_subtype_definitions(type_ir, ir, config))
   type_name = type_ir.name.name.text
   field_helper_type_definitions = []
   field_method_declarations = []
@@ -1221,6 +1238,15 @@
   else:
     requires_check = ""
 
+  if config.include_enum_traits:
+    text_stream_methods = code_template.format_template(
+      _TEMPLATES.struct_text_stream,
+      decode_fields="\n".join(decode_field_clauses),
+      write_fields="\n".join(write_field_clauses))
+  else:
+    text_stream_methods = ""
+
+
   class_forward_declarations = code_template.format_template(
       _TEMPLATES.structure_view_declaration,
       name=type_name)
@@ -1234,9 +1260,8 @@
       requires_check=requires_check,
       equals_method_body="\n".join(equals_method_clauses),
       unchecked_equals_method_body="\n".join(unchecked_equals_method_clauses),
-      decode_fields="\n".join(decode_field_clauses),
       enum_usings="\n".join(enum_using_statements),
-      write_fields="\n".join(write_field_clauses),
+      text_stream_methods=text_stream_methods,
       parameter_fields="\n".join(parameter_fields),
       constructor_parameters="".join(constructor_parameters),
       forwarded_parameters="".join(forwarded_parameters),
@@ -1314,7 +1339,7 @@
             for case in cases]
 
 
-def _generate_enum_definition(type_ir):
+def _generate_enum_definition(type_ir, include_traits=True):
   """Generates C++ for an Emboss enum."""
   enum_values = []
   enum_from_string_statements = []
@@ -1333,48 +1358,52 @@
           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))
-
-      if numeric_value not in previously_seen_numeric_values:
-        string_from_enum_statements.append(
-            code_template.format_template(_TEMPLATES.name_from_enum_case,
+      if include_traits:
+        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=enum_value_name))
+        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(
+
+  declaration = code_template.format_template(
           _TEMPLATES.enum_declaration,
           enum=type_ir.name.name.text,
-          enum_type=enum_type),
-      code_template.format_template(
+          enum_type=enum_type)
+  definition = code_template.format_template(
           _TEMPLATES.enum_definition,
           enum=type_ir.name.name.text,
           enum_type=enum_type,
-          enum_values="".join(enum_values),
+          enum_values="".join(enum_values))
+  if include_traits:
+    definition += code_template.format_template(
+          _TEMPLATES.enum_traits,
+          enum=type_ir.name.name.text,
           enum_from_name_cases="\n".join(enum_from_string_statements),
           name_from_enum_cases="\n".join(string_from_enum_statements),
-          enum_is_known_cases="\n".join(enum_is_known_statements)),
-      ""
-  )
+          enum_is_known_cases="\n".join(enum_is_known_statements))
+
+  return (declaration, definition, "")
 
 
-def _generate_type_definition(type_ir, ir):
+def _generate_type_definition(type_ir, ir, config: Config):
   """Generates C++ for an Emboss type."""
   if type_ir.HasField("structure"):
-    return _generate_structure_definition(type_ir, ir)
+    return _generate_structure_definition(type_ir, ir, config)
   elif type_ir.HasField("enumeration"):
-    return _generate_enum_definition(type_ir)
+    return _generate_enum_definition(type_ir, config.include_enum_traits)
   elif type_ir.HasField("external"):
     # TODO(bolms): This should probably generate an #include.
     return "", "", ""
@@ -1549,7 +1578,7 @@
   return []
 
 
-def generate_header(ir):
+def generate_header(ir, config=Config()):
   """Generates a C++ header from an Emboss module.
 
   Arguments:
@@ -1569,7 +1598,7 @@
   method_definitions = []
   for type_definition in ir.module[0].type:
     declaration, definition, methods = _generate_type_definition(
-        type_definition, ir)
+        type_definition, ir, config)
     type_declarations.append(declaration)
     type_definitions.append(definition)
     method_definitions.append(methods)
@@ -1579,7 +1608,7 @@
       type_definitions="".join(type_definitions),
       method_definitions="".join(method_definitions))
   body = _wrap_in_namespace(body, _get_module_namespace(ir.module[0]))
-  includes = _get_includes(ir.module[0])
+  includes = _get_includes(ir.module[0], config)
   return code_template.format_template(
       _TEMPLATES.outline,
       includes=includes,
diff --git a/compiler/back_end/cpp/testcode/no_enum_traits_test.cc b/compiler/back_end/cpp/testcode/no_enum_traits_test.cc
new file mode 100644
index 0000000..78dbd90
--- /dev/null
+++ b/compiler/back_end/cpp/testcode/no_enum_traits_test.cc
@@ -0,0 +1,48 @@
+// Copyright 2024 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 that an emb compiled with enable_enum_traits = False actually compiles.
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "testdata/no_enum_traits.emb.h"
+
+namespace emboss {
+namespace test {
+namespace {
+
+TEST(NoEnumTraits, Compiles) {
+  ::std::vector<uint8_t> backing_store(1);
+  auto view = MakeBarView(&backing_store);
+  view.foo().Write(Foo::VALUE);
+  EXPECT_TRUE(view.Ok());
+
+  // Check that we don't accidentally include `emboss_text_util.h` via our
+  // generated header.
+#ifdef EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_
+  const bool emboss_text_util_is_present = true;
+#else
+  const bool emboss_text_util_is_present = false;
+#endif
+
+  EXPECT_FALSE(emboss_text_util_is_present);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace emboss
diff --git a/embossc b/embossc
index 0dac79f..7bc0227 100755
--- a/embossc
+++ b/embossc
@@ -20,6 +20,7 @@
 import os
 import sys
 
+
 def _parse_args(argv):
   parser = argparse.ArgumentParser(description="Emboss compiler")
   parser.add_argument("--color-output",
@@ -49,6 +50,11 @@
                       nargs=1,
                       help="""File name to be used for the generated output
                               file. Defaults to input_file suffixed by '.h'""")
+  parser.add_argument("--cc-enum-traits",
+                      action=argparse.BooleanOptionalAction,
+                      default=True,
+                      help="""Controls generation of EnumTraits by the C++
+                              backend""")
   parser.add_argument("input_file",
                       type=str,
                       nargs=1,
@@ -62,7 +68,7 @@
   sys.path.append(base_path)
 
   from compiler.back_end.cpp import ( # pylint:disable=import-outside-toplevel
-    emboss_codegen_cpp
+    emboss_codegen_cpp, header_generator
   )
   from compiler.front_end import ( # pylint:disable=import-outside-toplevel
     emboss_front_end
@@ -74,8 +80,9 @@
   if errors:
     return 1
 
+  config = header_generator.Config(include_enum_traits=flags.cc_enum_traits)
   header, errors = emboss_codegen_cpp.generate_headers_and_log_errors(
-      ir, flags.color_output)
+      ir, flags.color_output, config)
 
   if errors:
     return 1
diff --git a/runtime/cpp/emboss_array_view.h b/runtime/cpp/emboss_array_view.h
index 7287fce..674b2ad 100644
--- a/runtime/cpp/emboss_array_view.h
+++ b/runtime/cpp/emboss_array_view.h
@@ -22,12 +22,16 @@
 #include <type_traits>
 
 #include "runtime/cpp/emboss_arithmetic.h"
-#include "runtime/cpp/emboss_array_view.h"
-#include "runtime/cpp/emboss_text_util.h"
 
 namespace emboss {
 
 // Forward declarations for use by WriteShorthandArrayCommentToTextStream.
+class TextOutputOptions;
+namespace support {
+template <class Array, class Stream>
+void WriteShorthandAsciiArrayCommentToTextStream(
+    const Array *array, Stream *stream, const TextOutputOptions &options);
+}
 namespace prelude {
 template <class Parameters, class BitViewType>
 class UIntView;
@@ -169,7 +173,7 @@
                           ElementViewIteratorDirection::kReverse>;
 
   GenericArrayView() : buffer_() {}
-  explicit GenericArrayView(const ElementViewParameterTypes &... parameters,
+  explicit GenericArrayView(const ElementViewParameterTypes &...parameters,
                             BufferType buffer)
       : parameters_{parameters...}, buffer_{buffer} {}
 
@@ -348,30 +352,6 @@
   static_cast<void>(options);
 }
 
-// Prints out the elements of an 8-bit Int or UInt array as characters.
-template <class Array, class Stream>
-void WriteShorthandAsciiArrayCommentToTextStream(
-    const Array *array, Stream *stream, const TextOutputOptions &options) {
-  if (!options.multiline()) return;
-  if (!options.comments()) return;
-  if (array->ElementCount() == 0) return;
-  static constexpr int kCharsPerBlock = 64;
-  static constexpr char kStandInForNonPrintableChar = '.';
-  auto start_new_line = [&]() {
-    stream->Write("\n");
-    stream->Write(options.current_indent());
-    stream->Write("# ");
-  };
-  for (int i = 0, n = array->ElementCount(); i < n; ++i) {
-    const int c = (*array)[i].Read();
-    const bool c_is_printable = (c >= 32 && c <= 126);
-    const bool starting_new_block = ((i % kCharsPerBlock) == 0);
-    if (starting_new_block) start_new_line();
-    stream->Write(c_is_printable ? static_cast<char>(c)
-                                 : kStandInForNonPrintableChar);
-  }
-}
-
 // Overload for arrays of UInt.
 // Prints out the elements as ASCII characters for arrays of UInt:8.
 template <class BufferType, class BitViewType, class Stream,
diff --git a/runtime/cpp/emboss_cpp_util.h b/runtime/cpp/emboss_cpp_util.h
index 27ef486..1e837ac 100644
--- a/runtime/cpp/emboss_cpp_util.h
+++ b/runtime/cpp/emboss_cpp_util.h
@@ -25,7 +25,6 @@
 #include "runtime/cpp/emboss_defines.h"
 #include "runtime/cpp/emboss_enum_view.h"
 #include "runtime/cpp/emboss_memory_util.h"
-#include "runtime/cpp/emboss_text_util.h"
 #include "runtime/cpp/emboss_view_parameters.h"
 
 #endif  // EMBOSS_RUNTIME_CPP_EMBOSS_CPP_UTIL_H_
diff --git a/runtime/cpp/emboss_enum_view.h b/runtime/cpp/emboss_enum_view.h
index af4d7aa..fa51f80 100644
--- a/runtime/cpp/emboss_enum_view.h
+++ b/runtime/cpp/emboss_enum_view.h
@@ -21,9 +21,21 @@
 #include <string>
 #include <utility>
 
-#include "runtime/cpp/emboss_text_util.h"
+#include "runtime/cpp/emboss_defines.h"
 #include "runtime/cpp/emboss_view_parameters.h"
 
+// Forward declarations for optional text processing helpers.
+namespace emboss {
+class TextOutputOptions;
+namespace support {
+template <class Stream, class View>
+bool ReadEnumViewFromTextStream(View *view, Stream *stream);
+template <class Stream, class View>
+void WriteEnumViewToTextStream(View *view, Stream *stream,
+                               const TextOutputOptions &options);
+}  // namespace support
+}  // namespace emboss
+
 namespace emboss {
 namespace support {
 
@@ -36,7 +48,7 @@
       Parameters::kBits <= sizeof(ValueType) * 8,
       "EnumView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
   template <typename... Args>
-  explicit EnumView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+  explicit EnumView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
   EnumView() : buffer_() {}
   EnumView(const EnumView &) = default;
   EnumView(EnumView &&) = default;
@@ -124,25 +136,7 @@
 
   template <class Stream>
   bool UpdateFromTextStream(Stream *stream) const {
-    ::std::string token;
-    if (!ReadToken(stream, &token)) return false;
-    if (token.empty()) return false;
-    if (::std::isdigit(token[0])) {
-      ::std::uint64_t value;
-      if (!DecodeInteger(token, &value)) return false;
-      // TODO(bolms): Fix the static_cast<ValueType> for signed ValueType.
-      // TODO(bolms): Should values between 2**63 and 2**64-1 actually be
-      // allowed in the text format when ValueType is signed?
-      return TryToWrite(static_cast<ValueType>(value));
-    } else if (token[0] == '-') {
-      ::std::int64_t value;
-      if (!DecodeInteger(token, &value)) return false;
-      return TryToWrite(static_cast<ValueType>(value));
-    } else {
-      ValueType value;
-      if (!TryToGetEnumFromName(token.c_str(), &value)) return false;
-      return TryToWrite(value);
-    }
+    return ::emboss::support::ReadEnumViewFromTextStream(this, stream);
   }
 
   template <class Stream>
diff --git a/runtime/cpp/emboss_prelude.h b/runtime/cpp/emboss_prelude.h
index 0fbaff4..8b5fa28 100644
--- a/runtime/cpp/emboss_prelude.h
+++ b/runtime/cpp/emboss_prelude.h
@@ -28,6 +28,30 @@
 
 #include "runtime/cpp/emboss_cpp_util.h"
 
+// Forward declarations for optional text processing helpers.
+namespace emboss {
+class TextOutputOptions;
+namespace support {
+template <class Stream, class View>
+bool ReadBooleanFromTextStream(View *view, Stream *stream);
+template <class Stream, class View>
+void WriteBooleanViewToTextStream(View *view, Stream *stream,
+                                  const TextOutputOptions &);
+
+template <class Stream, class View>
+bool ReadIntegerFromTextStream(View *view, Stream *stream);
+template <class Stream, class View>
+void WriteIntegerViewToTextStream(View *view, Stream *stream,
+                                  const TextOutputOptions &options);
+
+template <class Stream, class View>
+bool ReadFloatFromTextStream(View *view, Stream *stream);
+template <class Stream, class Float>
+void WriteFloatToTextStream(Float n, Stream *stream,
+                            const TextOutputOptions &options);
+}  // namespace support
+}  // namespace emboss
+
 // This namespace must match the [(cpp) namespace] in the Emboss prelude.
 namespace emboss {
 namespace prelude {
@@ -101,15 +125,7 @@
 
   template <class Stream>
   bool UpdateFromTextStream(Stream *stream) const {
-    ::std::string token;
-    if (!::emboss::support::ReadToken(stream, &token)) return false;
-    if (token == "true") {
-      return TryToWrite(true);
-    } else if (token == "false") {
-      return TryToWrite(false);
-    }
-    // TODO(bolms): Provide a way to get an error message on parse failure.
-    return false;
+    return ::emboss::support::ReadBooleanFromTextStream(this, stream);
   }
 
   template <class Stream>
@@ -136,7 +152,7 @@
       "UIntView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
 
   template <typename... Args>
-  explicit UIntView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+  explicit UIntView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
   UIntView() : buffer_() {}
   UIntView(const UIntView &) = default;
   UIntView(UIntView &&) = default;
@@ -295,7 +311,7 @@
 
   template <class Stream>
   void WriteToTextStream(Stream *stream,
-                         ::emboss::TextOutputOptions options) const {
+                         ::emboss::TextOutputOptions &options) const {
     support::WriteIntegerViewToTextStream(this, stream, options);
   }
 
@@ -318,7 +334,7 @@
                 "IntView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
 
   template <typename... Args>
-  explicit IntView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+  explicit IntView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
   IntView() : buffer_() {}
   IntView(const IntView &) = default;
   IntView(IntView &&) = default;
@@ -460,7 +476,7 @@
 
   template <class Stream>
   void WriteToTextStream(Stream *stream,
-                         ::emboss::TextOutputOptions options) const {
+                         ::emboss::TextOutputOptions &options) const {
     support::WriteIntegerViewToTextStream(this, stream, options);
   }
 
@@ -605,7 +621,7 @@
                 "BcdView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
 
   template <typename... Args>
-  explicit BcdView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+  explicit BcdView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
   BcdView() : buffer_() {}
   BcdView(const BcdView &) = default;
   BcdView(BcdView &&) = default;
@@ -645,7 +661,7 @@
 
   template <class Stream>
   void WriteToTextStream(Stream *stream,
-                         ::emboss::TextOutputOptions options) const {
+                         ::emboss::TextOutputOptions &options) const {
     // TODO(bolms): This shares the numeric_base() option with IntView and
     // UIntView (and EnumView, for unknown enum values).  It seems like an end
     // user might prefer to see BCD values in decimal, even if they want to see
@@ -728,8 +744,7 @@
   using ValueType = typename support::FloatType<Parameters::kBits>::Type;
 
   template <typename... Args>
-  explicit FloatView(Args &&... args)
-      : buffer_{::std::forward<Args>(args)...} {}
+  explicit FloatView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
   FloatView() : buffer_() {}
   FloatView(const FloatView &) = default;
   FloatView(FloatView &&) = default;
@@ -797,7 +812,7 @@
 
   template <class Stream>
   void WriteToTextStream(Stream *stream,
-                         ::emboss::TextOutputOptions options) const {
+                         ::emboss::TextOutputOptions &options) const {
     support::WriteFloatToTextStream(Read(), stream, options);
   }
 
diff --git a/runtime/cpp/emboss_text_util.h b/runtime/cpp/emboss_text_util.h
index ac2e1d9..9cfe03d 100644
--- a/runtime/cpp/emboss_text_util.h
+++ b/runtime/cpp/emboss_text_util.h
@@ -347,6 +347,19 @@
   }
 }
 
+template <class Stream, class View>
+bool ReadBooleanFromTextStream(View *view, Stream *stream) {
+  ::std::string token;
+  if (!::emboss::support::ReadToken(stream, &token)) return false;
+  if (token == "true") {
+    return view->TryToWrite(true);
+  } else if (token == "false") {
+    return view->TryToWrite(false);
+  }
+  // TODO(bolms): Provide a way to get an error message on parse failure.
+  return false;
+}
+
 // The TextOutputOptions parameter is present so that it can be passed in by
 // generated code that uses the same form for WriteBooleanViewToTextStream,
 // WriteIntegerViewToTextStream, and WriteEnumViewToTextStream.
@@ -596,6 +609,29 @@
 }
 
 template <class Stream, class View>
+bool ReadEnumViewFromTextStream(View *view, Stream *stream) {
+  ::std::string token;
+  if (!ReadToken(stream, &token)) return false;
+  if (token.empty()) return false;
+  if (::std::isdigit(token[0])) {
+    ::std::uint64_t value;
+    if (!DecodeInteger(token, &value)) return false;
+    // TODO(bolms): Fix the static_cast<ValueType> for signed ValueType.
+    // TODO(bolms): Should values between 2**63 and 2**64-1 actually be
+    // allowed in the text format when ValueType is signed?
+    return view->TryToWrite(static_cast<typename View::ValueType>(value));
+  } else if (token[0] == '-') {
+    ::std::int64_t value;
+    if (!DecodeInteger(token, &value)) return false;
+    return view->TryToWrite(static_cast<typename View::ValueType>(value));
+  } else {
+    typename View::ValueType value;
+    if (!TryToGetEnumFromName(token.c_str(), &value)) return false;
+    return view->TryToWrite(value);
+  }
+}
+
+template <class Stream, class View>
 void WriteEnumViewToTextStream(View *view, Stream *stream,
                                const TextOutputOptions &options) {
   const char *name = TryToGetNameFromEnum(view->Read());
@@ -682,6 +718,30 @@
   }
 }
 
+// Prints out the elements of an 8-bit Int or UInt array as characters.
+template <class Array, class Stream>
+void WriteShorthandAsciiArrayCommentToTextStream(
+    const Array *array, Stream *stream, const TextOutputOptions &options) {
+  if (!options.multiline()) return;
+  if (!options.comments()) return;
+  if (array->ElementCount() == 0) return;
+  static constexpr int kCharsPerBlock = 64;
+  static constexpr char kStandInForNonPrintableChar = '.';
+  auto start_new_line = [&]() {
+    stream->Write("\n");
+    stream->Write(options.current_indent());
+    stream->Write("# ");
+  };
+  for (int i = 0, n = array->ElementCount(); i < n; ++i) {
+    const int c = (*array)[i].Read();
+    const bool c_is_printable = (c >= 32 && c <= 126);
+    const bool starting_new_block = ((i % kCharsPerBlock) == 0);
+    if (starting_new_block) start_new_line();
+    stream->Write(c_is_printable ? static_cast<char>(c)
+                                 : kStandInForNonPrintableChar);
+  }
+}
+
 // Writes an array to a text stream.  This writes the array in a format
 // compatible with ReadArrayFromTextStream, above.  For multiline output, writes
 // one element per line.
diff --git a/runtime/cpp/test/emboss_array_view_test.cc b/runtime/cpp/test/emboss_array_view_test.cc
index e662779..1f632e8 100644
--- a/runtime/cpp/test/emboss_array_view_test.cc
+++ b/runtime/cpp/test/emboss_array_view_test.cc
@@ -20,6 +20,7 @@
 #include "absl/strings/str_format.h"
 #include "gtest/gtest.h"
 #include "runtime/cpp/emboss_prelude.h"
+#include "runtime/cpp/emboss_text_util.h"
 
 namespace emboss {
 namespace support {
diff --git a/runtime/cpp/test/emboss_cpp_util_google_integration_test.cc b/runtime/cpp/test/emboss_cpp_util_google_integration_test.cc
index 55a1daf..d56286f 100644
--- a/runtime/cpp/test/emboss_cpp_util_google_integration_test.cc
+++ b/runtime/cpp/test/emboss_cpp_util_google_integration_test.cc
@@ -15,6 +15,7 @@
 #include "absl/strings/string_view.h"
 #include "gtest/gtest.h"
 #include "runtime/cpp/emboss_cpp_util.h"
+#include "runtime/cpp/emboss_text_util.h"
 
 namespace emboss {
 namespace support {
diff --git a/runtime/cpp/test/emboss_enum_view_test.cc b/runtime/cpp/test/emboss_enum_view_test.cc
index 1127f49..dd2d8ac 100644
--- a/runtime/cpp/test/emboss_enum_view_test.cc
+++ b/runtime/cpp/test/emboss_enum_view_test.cc
@@ -16,6 +16,7 @@
 
 #include "gtest/gtest.h"
 #include "runtime/cpp/emboss_prelude.h"
+#include "runtime/cpp/emboss_text_util.h"
 
 namespace emboss {
 namespace support {
diff --git a/runtime/cpp/test/emboss_memory_util_test.cc b/runtime/cpp/test/emboss_memory_util_test.cc
index c4f9ed0..6974e59 100644
--- a/runtime/cpp/test/emboss_memory_util_test.cc
+++ b/runtime/cpp/test/emboss_memory_util_test.cc
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <array>
 #include <string>
 #if __cplusplus >= 201703L
 #include <string_view>
 #endif  // __cplusplus >= 201703L
 #include <vector>
 
-#include "runtime/cpp/emboss_memory_util.h"
-
 #include "gtest/gtest.h"
+#include "runtime/cpp/emboss_memory_util.h"
 #include "runtime/cpp/emboss_prelude.h"
 
 namespace emboss {
@@ -175,13 +175,19 @@
 // std::basic_string<> with non-default trailing template parameters.
 template <class T>
 struct NonstandardAllocator {
-  using value_type = typename ::std::allocator_traits<::std::allocator<T>>::value_type;
-  using pointer = typename ::std::allocator_traits<::std::allocator<T>>::pointer;
-  using const_pointer = typename ::std::allocator_traits<::std::allocator<T>>::const_pointer;
+  using value_type =
+      typename ::std::allocator_traits<::std::allocator<T>>::value_type;
+  using pointer =
+      typename ::std::allocator_traits<::std::allocator<T>>::pointer;
+  using const_pointer =
+      typename ::std::allocator_traits<::std::allocator<T>>::const_pointer;
   using reference = typename ::std::allocator<T>::value_type &;
-  using const_reference = const typename ::std::allocator_traits<::std::allocator<T>>::value_type &;
-  using size_type = typename ::std::allocator_traits<::std::allocator<T>>::size_type;
-  using difference_type = typename ::std::allocator_traits<::std::allocator<T>>::difference_type;
+  using const_reference =
+      const typename ::std::allocator_traits<::std::allocator<T>>::value_type &;
+  using size_type =
+      typename ::std::allocator_traits<::std::allocator<T>>::size_type;
+  using difference_type =
+      typename ::std::allocator_traits<::std::allocator<T>>::difference_type;
 
   template <class U>
   struct rebind {
diff --git a/runtime/cpp/test/emboss_prelude_test.cc b/runtime/cpp/test/emboss_prelude_test.cc
index 9fb1cb6..889daae 100644
--- a/runtime/cpp/test/emboss_prelude_test.cc
+++ b/runtime/cpp/test/emboss_prelude_test.cc
@@ -18,6 +18,7 @@
 
 #include "gtest/gtest.h"
 #include "runtime/cpp/emboss_cpp_util.h"
+#include "runtime/cpp/emboss_text_util.h"
 
 namespace emboss {
 namespace prelude {
diff --git a/testdata/BUILD b/testdata/BUILD
index a04b4a6..a886d39 100644
--- a/testdata/BUILD
+++ b/testdata/BUILD
@@ -242,6 +242,14 @@
 )
 
 emboss_cc_library(
+    name = "no_enum_traits_emboss",
+    srcs = [
+        "no_enum_traits.emb",
+    ],
+    enable_enum_traits = False,
+)
+
+emboss_cc_library(
     name = "start_size_range_emboss",
     srcs = [
         "start_size_range.emb",
diff --git a/testdata/no_enum_traits.emb b/testdata/no_enum_traits.emb
new file mode 100644
index 0000000..bbe295e
--- /dev/null
+++ b/testdata/no_enum_traits.emb
@@ -0,0 +1,24 @@
+# Copyright 2019 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.
+
+-- Test .emb to ensure that compilation succedes if enum traits are disabled.
+
+[$default byte_order: "LittleEndian"]
+[(cpp) namespace: "emboss::test"]
+
+enum Foo:
+  VALUE = 10
+
+struct Bar:
+  0 [+1] Foo foo
\ No newline at end of file