Initial open-source commit of Emboss.
diff --git a/public/BUILD b/public/BUILD
new file mode 100644
index 0000000..45810a9
--- /dev/null
+++ b/public/BUILD
@@ -0,0 +1,169 @@
+# 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.
+
+# Emboss public definitions.
+
+load(
+    ":build_defs.bzl",
+    "emboss_cc_util_test",
+)
+
+py_library(
+    name = "ir_pb2",
+    srcs = ["ir_pb2.py"],
+    visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "cpp_utils",
+    hdrs = [
+        "emboss_arithmetic.h",
+        "emboss_array_view.h",
+        "emboss_bit_util.h",
+        "emboss_constant_view.h",
+        "emboss_cpp_types.h",
+        "emboss_cpp_util.h",
+        "emboss_defines.h",
+        "emboss_enum_view.h",
+        "emboss_maybe.h",
+        "emboss_memory_util.h",
+        "emboss_prelude.h",
+        "emboss_text_util.h",
+        "emboss_view_parameters.h",
+    ],
+    deps = [
+    ],
+    visibility = ["//visibility:public"],
+)
+
+emboss_cc_util_test(
+    name = "emboss_prelude_test",
+    srcs = [
+        "emboss_prelude_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_arithmetic_test",
+    srcs = [
+        "emboss_arithmetic_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_array_view_test",
+    srcs = [
+        "emboss_array_view_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_absl//absl/strings:str_format",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_bit_util_test",
+    srcs = [
+        "emboss_bit_util_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_constant_view_test",
+    srcs = [
+        "emboss_constant_view_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_cpp_types_test",
+    srcs = [
+        "emboss_cpp_types_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_defines_test",
+    srcs = [
+        "emboss_defines_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_maybe_test",
+    srcs = [
+        "emboss_maybe_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_memory_util_test",
+    srcs = [
+        "emboss_memory_util_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+emboss_cc_util_test(
+    name = "emboss_text_util_test",
+    srcs = [
+        "emboss_text_util_test.cc",
+    ],
+    copts = ["-DEMBOSS_FORCE_ALL_CHECKS"],
+    deps = [
+        ":cpp_utils",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/public/build_defs.bzl b/public/build_defs.bzl
new file mode 100644
index 0000000..47ed4e1
--- /dev/null
+++ b/public/build_defs.bzl
@@ -0,0 +1,93 @@
+# 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.
+
+# -*- mode: python; -*-
+# vim:set ft=blazebuild:
+"""Build defs for Emboss.
+
+This file exports the emboss_cc_library rule, which accepts an .emb file and
+produces a corresponding C++ library.
+"""
+
+def emboss_cc_library(name, srcs, deps = [], visibility = None):
+    """Constructs a C++ library from an .emb file."""
+    if len(srcs) != 1:
+        fail(
+            "Must specify exactly one Emboss source file for emboss_cc_library.",
+            "srcs",
+        )
+
+    native.filegroup(
+        # The original .emb file must be visible to any other emboss_cc_library
+        # that specifies this emboss_cc_library in its deps.  This rule makes the
+        # original .emb available to dependent rules.
+        # TODO(bolms): As an optimization, use the precompiled IR instead of
+        # reparsing the raw .embs.
+        name = name + "__emb",
+        srcs = srcs,
+        visibility = visibility,
+    )
+
+    native.genrule(
+        # The generated header may be used in non-cc_library rules.
+        name = name + "_header",
+        tools = [
+            # TODO(bolms): Make "emboss" driver program.
+            "//front_end:emboss_front_end",
+            "//back_end/cpp:emboss_codegen_cpp",
+        ],
+        srcs = srcs + [dep + "__emb" for dep in deps],
+        cmd = ("$(location //front_end:emboss_front_end) " +
+               "--output-ir-to-stdout " +
+               "--import-dir=. " +
+               "--import-dir='$(GENDIR)' " +
+               "$(location {}) > $(@D)/$$(basename $(OUTS) .h).ir; " +
+               "$(location //back_end/cpp:emboss_codegen_cpp) " +
+               "< $(@D)/$$(basename $(OUTS) .h).ir > " +
+               "$(OUTS); " +
+               "rm $(@D)/$$(basename $(OUTS) .h).ir").format(") $location( ".join(srcs)),
+        outs = [src + ".h" for src in srcs],
+        # This rule should only be visible to the following rule.
+        visibility = ["//visibility:private"],
+    )
+
+    native.cc_library(
+        name = name,
+        hdrs = [
+            ":" + name + "_header",
+        ],
+        deps = deps + [
+            "//public:cpp_utils",
+        ],
+        visibility = visibility,
+    )
+
+# TODO(bolms): Maybe move this to a non-public build_defs?
+def emboss_cc_util_test(name, copts = [], **kwargs):
+    """Constructs two cc_test targets, with and without optimizations."""
+    native.cc_test(
+        name = name,
+        copts = copts,
+        **kwargs
+    )
+    native.cc_test(
+        name = name + "_no_opts",
+        copts = copts + [
+            # This is generally a dangerous flag for an individual target, but
+            # these tests do not depend on any other .cc files that might
+            # #include any Emboss headers.
+            "-DEMBOSS_NO_OPTIMIZATIONS",
+        ],
+        **kwargs
+    )
diff --git a/public/emboss_arithmetic.h b/public/emboss_arithmetic.h
new file mode 100644
index 0000000..bb73b7f
--- /dev/null
+++ b/public/emboss_arithmetic.h
@@ -0,0 +1,325 @@
+// 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.
+
+// Implementations for the operations and builtin functions in the Emboss
+// expression language.
+#ifndef EMBOSS_PUBLIC_EMBOSS_ARITHMETIC_H_
+#define EMBOSS_PUBLIC_EMBOSS_ARITHMETIC_H_
+
+#include <cstdint>
+#include <type_traits>
+
+#include "public/emboss_bit_util.h"
+#include "public/emboss_maybe.h"
+
+namespace emboss {
+namespace support {
+
+// Arithmetic operations
+//
+// Emboss arithmetic is performed by special-purpose functions, not (directly)
+// using C++ operators.  This allows Emboss to handle the minor differences
+// between the ways that Emboss operations are defined and the way that C++
+// operations are defined, and provides a convenient way to handle arithmetic on
+// values that might not be readable.
+//
+// The biggest differences are:
+//
+// Emboss's And and Or are defined to return false or true, respectively, if at
+// least one operand is false or true, respectively, even if the other operand
+// is not Known().  This is similar to C/C++ shortcut evaluation, except that it
+// is symmetric.
+//
+// Emboss's expression type system uses (notionally) infinite-size integers, but
+// it is an error in Emboss if the full range of any subexpression cannot fit in
+// either [-(2**63), 2**63 - 1] or [0, 2**64 - 1].  Additionally, either all
+// arguments to and the return type of an operation, if integers, must fit in
+// int64_t, or they must all fit in uin64_t.  This means that C++ integer types
+// can be used directly for each operation, but casting may be required in
+// between operations.
+
+inline constexpr bool AllKnown() { return true; }
+
+template <typename T, typename... RestT>
+inline constexpr bool AllKnown(T value, RestT... rest) {
+  return value.Known() && AllKnown(rest...);
+}
+
+// MaybeDo implements the logic of checking for known values, unwrapping the
+// known values, passing the unwrapped values to OperatorT, and then rewrapping
+// the result.
+template <typename IntermediateT, typename ResultT, typename OperatorT,
+          typename... ArgsT>
+inline constexpr Maybe<ResultT> MaybeDo(Maybe<ArgsT>... args) {
+  return AllKnown(args...)
+             ? Maybe<ResultT>(static_cast<ResultT>(OperatorT::template Do(
+                   static_cast<IntermediateT>(args.ValueOrDefault())...)))
+             : Maybe<ResultT>();
+}
+
+//// Operations intended to be passed to MaybeDo:
+
+struct SumOperation {
+  template <typename T>
+  static inline constexpr T Do(T l, T r) {
+    return l + r;
+  }
+};
+
+struct DifferenceOperation {
+  template <typename T>
+  static inline constexpr T Do(T l, T r) {
+    return l - r;
+  }
+};
+
+struct ProductOperation {
+  template <typename T>
+  static inline constexpr T Do(T l, T r) {
+    return l * r;
+  }
+};
+
+// Assertions for the template types of comparisons.
+template <typename ResultT, typename LeftT, typename RightT>
+inline constexpr bool AssertComparisonInPartsTypes() {
+  static_assert(::std::is_same<ResultT, bool>::value,
+                "EMBOSS BUG: Comparisons must return bool.");
+  static_assert(
+      ::std::is_signed<LeftT>::value || ::std::is_signed<RightT>::value,
+      "EMBOSS BUG: Comparisons in parts expect one side to be signed.");
+  static_assert(
+      ::std::is_unsigned<LeftT>::value || ::std::is_unsigned<RightT>::value,
+      "EMBOSS BUG: Comparisons in parts expect one side to be unsigned.");
+  return true;  // A literal return type is required for a constexpr function.
+}
+
+struct EqualOperation {
+  template <typename T>
+  static inline constexpr bool Do(T l, T r) {
+    return l == r;
+  }
+};
+
+struct NotEqualOperation {
+  template <typename T>
+  static inline constexpr bool Do(T l, T r) {
+    return l != r;
+  }
+};
+
+struct LessThanOperation {
+  template <typename T>
+  static inline constexpr bool Do(T l, T r) {
+    return l < r;
+  }
+};
+
+struct LessThanOrEqualOperation {
+  template <typename T>
+  static inline constexpr bool Do(T l, T r) {
+    return l <= r;
+  }
+};
+
+struct GreaterThanOperation {
+  template <typename T>
+  static inline constexpr bool Do(T l, T r) {
+    return l > r;
+  }
+};
+
+struct GreaterThanOrEqualOperation {
+  template <typename T>
+  static inline constexpr bool Do(T l, T r) {
+    return l >= r;
+  }
+};
+
+// MaximumOperation is a bit more complex, in order to handle the variable
+// number of parameters.
+struct MaximumOperation {
+  template <typename T>
+  static inline constexpr T Do(T arg) {
+    // Base case for recursive template.
+    return arg;
+  }
+
+  // Ideally, this would only use template<typename T>, but C++11 requires a
+  // full variadic template or C-style variadic function in order to accept a
+  // variable number of arguments.  C-style variadic functions have no intrinsic
+  // way of figuring out how many arguments they receive, so we have to use a
+  // variadic template.
+  //
+  // The static_assert ensures that all arguments are actually the same type.
+  template <typename T1, typename T2, typename... T>
+  static inline constexpr T1 Do(T1 l, T2 r, T... rest) {
+    // C++11 std::max is not constexpr, so we can't just call it.
+    static_assert(::std::is_same<T1, T2>::value,
+                  "Expected Do to be called with a proper intermediate type.");
+    return Do(l < r ? r : l, rest...);
+  }
+};
+
+//// Special operations, where either un-Known() operands do not always result
+//// in un-Known() results, or where Known() operands do not always result in
+//// Known() results.
+
+// Assertions for And and Or.
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr bool AssertBooleanOperationTypes() {
+  // And and Or are templates so that the Emboss code generator
+  // doesn't have to special case AND, but they should only be instantiated with
+  // <bool, bool, bool>.  This pushes a bit of extra work onto the C++ compiler.
+  static_assert(::std::is_same<IntermediateT, bool>::value,
+                "EMBOSS BUG: Boolean operations must have bool IntermediateT.");
+  static_assert(::std::is_same<ResultT, bool>::value,
+                "EMBOSS BUG: Boolean operations must return bool.");
+  static_assert(::std::is_same<LeftT, bool>::value,
+                "EMBOSS BUG: Boolean operations require boolean operands.");
+  static_assert(::std::is_same<RightT, bool>::value,
+                "EMBOSS BUG: Boolean operations require boolean operands.");
+  return true;  // A literal return type is required for a constexpr function.
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> And(Maybe<LeftT> l, Maybe<RightT> r) {
+  // If either value is false, the result is false, even if the other value is
+  // unknown.  Otherwise, if either value is unknown, the result is unknown.
+  // Otherwise, both values are true, and the result is true.
+  return AssertBooleanOperationTypes<IntermediateT, ResultT, LeftT, RightT>(),
+         !l.ValueOr(true) || !r.ValueOr(true)
+             ? Maybe<ResultT>(false)
+             : (!l.Known() || !r.Known() ? Maybe<ResultT>()
+                                         : Maybe<ResultT>(true));
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> Or(Maybe<LeftT> l, Maybe<RightT> r) {
+  // If either value is true, the result is true, even if the other value is
+  // unknown.  Otherwise, if either value is unknown, the result is unknown.
+  // Otherwise, both values are false, and the result is false.
+  return AssertBooleanOperationTypes<IntermediateT, ResultT, LeftT, RightT>(),
+         l.ValueOr(false) || r.ValueOr(false)
+             ? Maybe<ResultT>(true)
+             : (!l.Known() || !r.Known() ? Maybe<ResultT>()
+                                         : Maybe<ResultT>(false));
+}
+
+template <typename ResultT, typename ValueT>
+inline constexpr Maybe<ResultT> MaybeStaticCast(Maybe<ValueT> value) {
+  return value.Known()
+             ? Maybe<ResultT>(static_cast<ResultT>(value.ValueOrDefault()))
+             : Maybe<ResultT>();
+}
+
+template <typename IntermediateT, typename ResultT, typename ConditionT,
+          typename TrueT, typename FalseT>
+inline constexpr Maybe<ResultT> Choice(Maybe<ConditionT> condition,
+                                       Maybe<TrueT> if_true,
+                                       Maybe<FalseT> if_false) {
+  // Since the result of a condition could be any value from either if_true or
+  // if_false, it should be the same type as IntermediateT.
+  static_assert(::std::is_same<IntermediateT, ResultT>::value,
+                "Choice's IntermediateT should be the same as ResultT.");
+  static_assert(::std::is_same<ConditionT, bool>::value,
+                "Choice operation requires a boolean condition.");
+  // If the condition is un-Known(), then the result is un-Known().  Otherwise,
+  // the result is if_true if condition, or if_false if not condition.  For
+  // integral types, ResultT may differ from TrueT or FalseT, so Known() results
+  // must be unwrapped, cast to ResultT, and re-wrapped in Maybe<ResultT>.  For
+  // non-integral TrueT/FalseT/ResultT, the cast is unnecessary, but safe.
+  return condition.Known() ? condition.ValueOrDefault()
+                                 ? MaybeStaticCast<ResultT, TrueT>(if_true)
+                                 : MaybeStaticCast<ResultT, FalseT>(if_false)
+                           : Maybe<ResultT>();
+}
+
+//// From here down: boilerplate instantiations of the various operations, which
+//// only forward to MaybeDo:
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> Sum(Maybe<LeftT> l, Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, SumOperation, LeftT, RightT>(l, r);
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> Difference(Maybe<LeftT> l, Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, DifferenceOperation, LeftT, RightT>(l,
+                                                                             r);
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> Product(Maybe<LeftT> l, Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, ProductOperation, LeftT, RightT>(l, r);
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> Equal(Maybe<LeftT> l, Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, EqualOperation, LeftT, RightT>(l, r);
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> NotEqual(Maybe<LeftT> l, Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, NotEqualOperation, LeftT, RightT>(l,
+                                                                           r);
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> LessThan(Maybe<LeftT> l, Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, LessThanOperation, LeftT, RightT>(l,
+                                                                           r);
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> LessThanOrEqual(Maybe<LeftT> l,
+                                                Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, LessThanOrEqualOperation, LeftT,
+                 RightT>(l, r);
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> GreaterThan(Maybe<LeftT> l, Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, GreaterThanOperation, LeftT, RightT>(
+      l, r);
+}
+
+template <typename IntermediateT, typename ResultT, typename LeftT,
+          typename RightT>
+inline constexpr Maybe<ResultT> GreaterThanOrEqual(Maybe<LeftT> l,
+                                                   Maybe<RightT> r) {
+  return MaybeDo<IntermediateT, ResultT, GreaterThanOrEqualOperation, LeftT,
+                 RightT>(l, r);
+}
+
+template <typename IntermediateT, typename ResultT, typename... ArgsT>
+inline constexpr Maybe<ResultT> Maximum(Maybe<ArgsT>... args) {
+  return MaybeDo<IntermediateT, ResultT, MaximumOperation, ArgsT...>(args...);
+}
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_ARITHMETIC_H_
diff --git a/public/emboss_arithmetic_test.cc b/public/emboss_arithmetic_test.cc
new file mode 100644
index 0000000..3f41315
--- /dev/null
+++ b/public/emboss_arithmetic_test.cc
@@ -0,0 +1,291 @@
+// 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.
+
+#include "public/emboss_arithmetic.h"
+
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+
+// EXPECT_EQ uses operator==.  For un-Known() Maybes, this follows the semantics
+// for operator==(std::optional<T>, std::optional<T>), which returns true if
+// neither argument has_value().  (It also matches Rust's Option and Haskell's
+// Maybe.)
+//
+// Given the name "Known", it arguably should follow NaN != NaN semantics
+// instead, but this is more useful for tests.
+template <typename T>
+constexpr inline bool operator==(const Maybe<T> &l, const Maybe<T> &r) {
+  return l.Known() == r.Known() && l.ValueOrDefault() == r.ValueOrDefault();
+}
+
+namespace test {
+
+using ::std::int32_t;
+using ::std::int64_t;
+using ::std::uint32_t;
+using ::std::uint64_t;
+
+TEST(Sum, Sum) {
+  EXPECT_EQ(Maybe<int32_t>(0), (Sum<int32_t, int32_t, int32_t, int32_t>(
+                                   Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<int32_t>(2147483647),
+            (Sum<int32_t, int32_t, int32_t, int32_t>(Maybe<int32_t>(2147483646),
+                                                     Maybe<int32_t>(1))));
+  EXPECT_EQ(Maybe<int32_t>(-2147483647 - 1),
+            (Sum<int32_t, int32_t, int32_t, int32_t>(
+                Maybe<int32_t>(-2147483647), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<uint32_t>(2147483648U),
+            (Sum<uint32_t, uint32_t, int32_t, int32_t>(
+                Maybe<int32_t>(2147483647), Maybe<int32_t>(1))));
+  EXPECT_EQ(Maybe<int32_t>(2147483647),
+            (Sum<int64_t, int32_t, uint32_t, int32_t>(
+                Maybe<uint32_t>(2147483648U), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<int32_t>(), (Sum<int64_t, int32_t, uint32_t, int32_t>(
+                                  Maybe<uint32_t>(), Maybe<int32_t>(-1))));
+}
+
+TEST(Difference, Difference) {
+  EXPECT_EQ(Maybe<int32_t>(0), (Difference<int32_t, int32_t, int32_t, int32_t>(
+                                   Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<int32_t>(2147483647),
+            (Difference<int32_t, int32_t, int32_t, int32_t>(
+                Maybe<int32_t>(2147483646), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<int32_t>(-2147483647 - 1),
+            (Difference<int32_t, int32_t, int32_t, int32_t>(
+                Maybe<int32_t>(-2147483647), Maybe<int32_t>(1))));
+  EXPECT_EQ(Maybe<uint32_t>(2147483648U),
+            (Difference<uint32_t, uint32_t, int32_t, int32_t>(
+                Maybe<int32_t>(2147483647), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<int32_t>(2147483647),
+            (Difference<uint32_t, int32_t, uint32_t, int32_t>(
+                Maybe<uint32_t>(2147483648U), Maybe<int32_t>(1))));
+  EXPECT_EQ(Maybe<int32_t>(-2147483647 - 1),
+            (Difference<int64_t, int32_t, int32_t, uint32_t>(
+                Maybe<int32_t>(1), Maybe<uint32_t>(2147483649U))));
+  EXPECT_EQ(Maybe<int32_t>(), (Difference<int64_t, int32_t, int32_t, uint32_t>(
+                                  Maybe<int32_t>(1), Maybe<uint32_t>())));
+}
+
+TEST(Product, Product) {
+  EXPECT_EQ(Maybe<int32_t>(0), (Product<int32_t, int32_t, int32_t, int32_t>(
+                                   Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<int32_t>(-2147483646),
+            (Product<int32_t, int32_t, int32_t, int32_t>(
+                Maybe<int32_t>(2147483646), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<int32_t>(-2147483647 - 1),
+            (Product<int32_t, int32_t, int32_t, int32_t>(
+                Maybe<int32_t>(-2147483647 - 1), Maybe<int32_t>(1))));
+  EXPECT_EQ(Maybe<uint32_t>(2147483648U),
+            (Product<uint32_t, uint32_t, int32_t, int32_t>(
+                Maybe<int32_t>(1073741824), Maybe<int32_t>(2))));
+  EXPECT_EQ(Maybe<uint32_t>(), (Product<uint32_t, uint32_t, int32_t, int32_t>(
+                                   Maybe<int32_t>(), Maybe<int32_t>(2))));
+}
+
+TEST(Equal, Equal) {
+  EXPECT_EQ(Maybe<bool>(true), (Equal<int32_t, bool, int32_t, int32_t>(
+                                   Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (Equal<int32_t, bool, int32_t, int32_t>(Maybe<int32_t>(2147483646),
+                                                    Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (Equal<int32_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (Equal<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U))));
+  EXPECT_EQ(Maybe<bool>(),
+            (Equal<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(), Maybe<uint32_t>(2147483648U))));
+}
+
+TEST(NotEqual, NotEqual) {
+  EXPECT_EQ(Maybe<bool>(false), (NotEqual<int32_t, bool, int32_t, int32_t>(
+                                    Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (NotEqual<int32_t, bool, int32_t, int32_t>(
+                Maybe<int32_t>(2147483646), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (NotEqual<int32_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (NotEqual<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U))));
+  EXPECT_EQ(Maybe<bool>(),
+            (NotEqual<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>())));
+}
+
+TEST(LessThan, LessThan) {
+  EXPECT_EQ(Maybe<bool>(false), (LessThan<int32_t, bool, int32_t, int32_t>(
+                                    Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (LessThan<int32_t, bool, int32_t, int32_t>(
+                Maybe<int32_t>(2147483646), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (LessThan<int32_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (LessThan<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U))));
+  EXPECT_EQ(Maybe<bool>(),
+            (LessThan<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(), Maybe<uint32_t>(2147483648U))));
+}
+
+TEST(LessThanOrEqual, LessThanOrEqual) {
+  EXPECT_EQ(Maybe<bool>(true),
+            (LessThanOrEqual<int32_t, bool, int32_t, int32_t>(
+                Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (LessThanOrEqual<int32_t, bool, int32_t, int32_t>(
+                Maybe<int32_t>(2147483646), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (LessThanOrEqual<int32_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (LessThanOrEqual<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U))));
+  EXPECT_EQ(Maybe<bool>(),
+            (LessThanOrEqual<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(), Maybe<uint32_t>(2147483648U))));
+}
+
+TEST(GreaterThan, GreaterThan) {
+  EXPECT_EQ(Maybe<bool>(false), (GreaterThan<int32_t, bool, int32_t, int32_t>(
+                                    Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (GreaterThan<int32_t, bool, int32_t, int32_t>(
+                Maybe<int32_t>(2147483646), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (GreaterThan<int32_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (GreaterThan<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U))));
+  EXPECT_EQ(Maybe<bool>(),
+            (GreaterThan<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(), Maybe<uint32_t>(2147483648U))));
+}
+
+TEST(GreaterThanOrEqual, GreaterThanOrEqual) {
+  EXPECT_EQ(Maybe<bool>(true),
+            (GreaterThanOrEqual<int32_t, bool, int32_t, int32_t>(
+                Maybe<int32_t>(0), Maybe<int32_t>(0))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (GreaterThanOrEqual<int32_t, bool, int32_t, int32_t>(
+                Maybe<int32_t>(2147483646), Maybe<int32_t>(-1))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (GreaterThanOrEqual<int32_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(2147483647), Maybe<uint32_t>(2147483647))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (GreaterThanOrEqual<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(-2147483648LL), Maybe<uint32_t>(2147483648U))));
+  EXPECT_EQ(Maybe<bool>(),
+            (GreaterThanOrEqual<int64_t, bool, int32_t, uint32_t>(
+                Maybe<int32_t>(), Maybe<uint32_t>(2147483648U))));
+}
+
+TEST(And, And) {
+  EXPECT_EQ(Maybe<bool>(true), (And<bool, bool, bool, bool>(
+                                   Maybe<bool>(true), Maybe<bool>(true))));
+  EXPECT_EQ(Maybe<bool>(),
+            (And<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>(true))));
+  EXPECT_EQ(Maybe<bool>(),
+            (And<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>())));
+  EXPECT_EQ(Maybe<bool>(),
+            (And<bool, bool, bool, bool>(Maybe<bool>(true), Maybe<bool>())));
+  EXPECT_EQ(Maybe<bool>(false), (And<bool, bool, bool, bool>(
+                                    Maybe<bool>(false), Maybe<bool>(true))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (And<bool, bool, bool, bool>(Maybe<bool>(false), Maybe<bool>())));
+  EXPECT_EQ(Maybe<bool>(false), (And<bool, bool, bool, bool>(
+                                    Maybe<bool>(false), Maybe<bool>(false))));
+  EXPECT_EQ(Maybe<bool>(false), (And<bool, bool, bool, bool>(
+                                    Maybe<bool>(true), Maybe<bool>(false))));
+  EXPECT_EQ(Maybe<bool>(false),
+            (And<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>(false))));
+}
+
+TEST(Or, Or) {
+  EXPECT_EQ(Maybe<bool>(false), (Or<bool, bool, bool, bool>(
+                                    Maybe<bool>(false), Maybe<bool>(false))));
+  EXPECT_EQ(Maybe<bool>(),
+            (Or<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>(false))));
+  EXPECT_EQ(Maybe<bool>(),
+            (Or<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>())));
+  EXPECT_EQ(Maybe<bool>(),
+            (Or<bool, bool, bool, bool>(Maybe<bool>(false), Maybe<bool>())));
+  EXPECT_EQ(Maybe<bool>(true), (Or<bool, bool, bool, bool>(Maybe<bool>(false),
+                                                           Maybe<bool>(true))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (Or<bool, bool, bool, bool>(Maybe<bool>(true), Maybe<bool>())));
+  EXPECT_EQ(Maybe<bool>(true),
+            (Or<bool, bool, bool, bool>(Maybe<bool>(true), Maybe<bool>(true))));
+  EXPECT_EQ(Maybe<bool>(true), (Or<bool, bool, bool, bool>(
+                                   Maybe<bool>(true), Maybe<bool>(false))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (Or<bool, bool, bool, bool>(Maybe<bool>(), Maybe<bool>(true))));
+}
+
+TEST(Choice, Choice) {
+  EXPECT_EQ(Maybe<int>(), (Choice<int, int, bool, int, int>(
+                              Maybe<bool>(), Maybe<int>(1), Maybe<int>(2))));
+  EXPECT_EQ(Maybe<int>(1),
+            (Choice<int, int, bool, int, int>(Maybe<bool>(true), Maybe<int>(1),
+                                              Maybe<int>(2))));
+  EXPECT_EQ(Maybe<int>(2),
+            (Choice<int, int, bool, int, int>(Maybe<bool>(false), Maybe<int>(1),
+                                              Maybe<int>(2))));
+  EXPECT_EQ(Maybe<int>(), (Choice<int, int, bool, int, int>(
+                              Maybe<bool>(true), Maybe<int>(), Maybe<int>(2))));
+  EXPECT_EQ(Maybe<int>(),
+            (Choice<int, int, bool, int, int>(Maybe<bool>(false), Maybe<int>(1),
+                                              Maybe<int>())));
+  EXPECT_EQ(Maybe<int64_t>(2),
+            (Choice<int64_t, int64_t, bool, int32_t, int32_t>(
+                Maybe<bool>(false), Maybe<int32_t>(1), Maybe<int32_t>(2))));
+  EXPECT_EQ(Maybe<int64_t>(2),
+            (Choice<int64_t, int64_t, bool, int32_t, uint32_t>(
+                Maybe<bool>(false), Maybe<int32_t>(-1), Maybe<uint32_t>(2))));
+  EXPECT_EQ(Maybe<int64_t>(-1),
+            (Choice<int64_t, int64_t, bool, int32_t, uint32_t>(
+                Maybe<bool>(true), Maybe<int32_t>(-1), Maybe<uint32_t>(2))));
+  EXPECT_EQ(Maybe<bool>(true),
+            (Choice<bool, bool, bool, bool, bool>(
+                Maybe<bool>(false), Maybe<bool>(false), Maybe<bool>(true))));
+}
+
+TEST(Maximum, Maximum) {
+  EXPECT_EQ(Maybe<int>(100), (Maximum<int, int, int>(Maybe<int>(100))));
+  EXPECT_EQ(Maybe<int>(99),
+            (Maximum<int, int, int, int>(Maybe<int>(99), Maybe<int>(50))));
+  EXPECT_EQ(Maybe<int>(98),
+            (Maximum<int, int, int, int>(Maybe<int>(50), Maybe<int>(98))));
+  EXPECT_EQ(Maybe<int>(97),
+            (Maximum<int, int, int, int, int>(Maybe<int>(50), Maybe<int>(70),
+                                              Maybe<int>(97))));
+  EXPECT_EQ(Maybe<int>(), (Maximum<int, int, int, int, int>(
+                              Maybe<int>(50), Maybe<int>(), Maybe<int>(97))));
+  EXPECT_EQ(Maybe<int>(-100),
+            (Maximum<int, int, int, int, int>(
+                Maybe<int>(-120), Maybe<int>(-150), Maybe<int>(-100))));
+  EXPECT_EQ(Maybe<int>(), (Maximum<int, int, int>(Maybe<int>())));
+}
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_array_view.h b/public/emboss_array_view.h
new file mode 100644
index 0000000..12f0a59
--- /dev/null
+++ b/public/emboss_array_view.h
@@ -0,0 +1,382 @@
+// 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.
+
+// View classes for arrays and bit arrays.
+#ifndef EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_
+#define EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_
+
+#include <cstddef>
+#include <iterator>
+#include <tuple>
+#include <type_traits>
+
+#include "public/emboss_arithmetic.h"
+#include "public/emboss_array_view.h"
+#include "public/emboss_text_util.h"
+
+namespace emboss {
+
+// Forward declarations for use by WriteShorthandArrayCommentToTextStream.
+namespace prelude {
+template <class Parameters, class BitViewType>
+class UIntView;
+template <class Parameters, class BitViewType>
+class IntView;
+}  // namespace prelude
+
+namespace support {
+
+// Advance direction for ElementViewIterator.
+enum class ElementViewIteratorDirection { kForward, kReverse };
+
+// Iterator adapter for elements in a GenericArrayView.
+template <class GenericArrayView, ElementViewIteratorDirection kDirection>
+class ElementViewIterator {
+ public:
+  using iterator_category = ::std::random_access_iterator_tag;
+  using value_type = typename GenericArrayView::ViewType;
+  using difference_type = ::std::ptrdiff_t;
+  using pointer = typename ::std::add_pointer<value_type>::type;
+  using reference = typename ::std::add_lvalue_reference<value_type>::type;
+
+  explicit ElementViewIterator(const GenericArrayView *array_view,
+                               ::std::ptrdiff_t index)
+      : array_view_(array_view), view_((*array_view)[index]), index_(index) {}
+
+  ElementViewIterator() = default;
+
+  reference operator*() { return view_; }
+
+  pointer operator->() { return &view_; }
+
+  ElementViewIterator &operator+=(difference_type d) {
+    index_ += (kDirection == ElementViewIteratorDirection::kForward ? d : -d);
+    view_ = (*array_view_)[index_];
+    return *this;
+  }
+
+  ElementViewIterator &operator-=(difference_type d) { return *this += (-d); }
+
+  ElementViewIterator &operator++() {
+    *this += 1;
+    return *this;
+  }
+
+  ElementViewIterator &operator--() {
+    *this -= 1;
+    return *this;
+  }
+
+  ElementViewIterator operator++(int) {
+    auto copy = *this;
+    ++(*this);
+    return copy;
+  }
+
+  ElementViewIterator operator--(int) {
+    auto copy = *this;
+    --(*this);
+    return copy;
+  }
+
+  ElementViewIterator operator+(difference_type d) const {
+    auto copy = *this;
+    copy += d;
+    return copy;
+  }
+
+  ElementViewIterator operator-(difference_type d) const {
+    return *this + (-d);
+  }
+
+  difference_type operator-(const ElementViewIterator &other) const {
+    return kDirection == ElementViewIteratorDirection::kForward
+               ? index_ - other.index_
+               : other.index_ - index_;
+  }
+
+  bool operator==(const ElementViewIterator &other) const {
+    return array_view_ == other.array_view_ && index_ == other.index_;
+  }
+
+  bool operator!=(const ElementViewIterator &other) const {
+    return !(*this == other);
+  }
+
+  bool operator<(const ElementViewIterator &other) const {
+    return kDirection == ElementViewIteratorDirection::kForward
+               ? index_ < other.index_
+               : other.index_ < index_;
+  }
+
+  bool operator<=(const ElementViewIterator &other) const {
+    return kDirection == ElementViewIteratorDirection::kForward
+               ? index_ <= other.index_
+               : other.index_ <= index_;
+  }
+
+  bool operator>(const ElementViewIterator &other) const {
+    return !(*this <= other);
+  }
+
+  bool operator>=(const ElementViewIterator &other) const {
+    return !(*this < other);
+  }
+
+ private:
+  const GenericArrayView *array_view_;
+  typename GenericArrayView::ViewType view_;
+  ::std::ptrdiff_t index_;
+};
+
+// View for an array in a structure.
+//
+// ElementView should be the view class for a single array element (e.g.,
+// UIntView<...> or ArrayView<...>).
+//
+// BufferType is the storage type that will be passed into the array.
+//
+// kElementSize is the fixed size of a single element, in addressable units.
+//
+// kAddressableUnitSize is the size of a single addressable unit.  It should be
+// either 1 (one bit) or 8 (one byte).
+//
+// ElementViewParameterTypes is a list of the types of parameters which must be
+// passed down to each element of the array.  ElementViewParameterTypes can be
+// empty.
+template <class ElementView, class BufferType, ::std::size_t kElementSize,
+          ::std::size_t kAddressableUnitSize,
+          typename... ElementViewParameterTypes>
+class GenericArrayView final {
+ public:
+  using ViewType = ElementView;
+  using ForwardIterator =
+      ElementViewIterator<GenericArrayView,
+                          ElementViewIteratorDirection::kForward>;
+  using ReverseIterator =
+      ElementViewIterator<GenericArrayView,
+                          ElementViewIteratorDirection::kReverse>;
+
+  GenericArrayView() : buffer_() {}
+  explicit GenericArrayView(const ElementViewParameterTypes &... parameters,
+                            BufferType buffer)
+      : parameters_{parameters...}, buffer_{buffer} {}
+
+  ElementView operator[](::std::size_t index) const {
+    return IndexOperatorHelper<sizeof...(ElementViewParameterTypes) ==
+                               0>::ConstructElement(parameters_, buffer_,
+                                                    index);
+  }
+
+  ForwardIterator begin() const { return ForwardIterator(this, 0); }
+  ForwardIterator end() const { return ForwardIterator(this, ElementCount()); }
+  ReverseIterator rbegin() const {
+    return ReverseIterator(this, ElementCount() - 1);
+  }
+  ReverseIterator rend() const { return ReverseIterator(this, -1); }
+
+  // In order to selectively enable SizeInBytes and SizeInBits, it is
+  // necessary to make them into templates.  Further, it is necessary for
+  // ::std::enable_if to have a dependency on the template parameter, otherwise
+  // SFINAE won't kick in.  Thus, these are templated on an int, and that int
+  // is (spuriously) used as the left argument to `,` in the enable_if
+  // condition.  The explicit cast to void is needed to silence GCC's
+  // -Wunused-value.
+  template <int N = 0>
+  typename ::std::enable_if<((void)N, kAddressableUnitSize == 8),
+                            ::std::size_t>::type
+  SizeInBytes() const {
+    return buffer_.SizeInBytes();
+  }
+  template <int N = 0>
+  typename ::std::enable_if<((void)N, kAddressableUnitSize == 1),
+                            ::std::size_t>::type
+  SizeInBits() const {
+    return buffer_.SizeInBits();
+  }
+
+  ::std::size_t ElementCount() const { return SizeOfBuffer() / kElementSize; }
+  bool Ok() const {
+    if (!buffer_.Ok()) return false;
+    if (SizeOfBuffer() % kElementSize != 0) return false;
+    for (::std::size_t i = 0; i < ElementCount(); ++i) {
+      if (!(*this)[i].Ok()) return false;
+    }
+    return true;
+  }
+  template <class OtherElementView, class OtherBufferType>
+  bool Equals(
+      const GenericArrayView<OtherElementView, OtherBufferType, kElementSize,
+                             kAddressableUnitSize> &other) const {
+    if (ElementCount() != other.ElementCount()) return false;
+    for (::std::size_t i = 0; i < ElementCount(); ++i) {
+      if (!(*this)[i].Equals(other[i])) return false;
+    }
+    return true;
+  }
+  template <class OtherElementView, class OtherBufferType>
+  bool UncheckedEquals(
+      const GenericArrayView<OtherElementView, OtherBufferType, kElementSize,
+                             kAddressableUnitSize> &other) const {
+    if (ElementCount() != other.ElementCount()) return false;
+    for (::std::size_t i = 0; i < ElementCount(); ++i) {
+      if (!(*this)[i].UncheckedEquals(other[i])) return false;
+    }
+    return true;
+  }
+  bool IsComplete() const { return buffer_.Ok(); }
+
+  template <class Stream>
+  bool UpdateFromTextStream(Stream *stream) const {
+    return ReadArrayFromTextStream(this, stream);
+  }
+
+  template <class Stream>
+  void WriteToTextStream(Stream *stream,
+                         const TextOutputOptions &options) const {
+    WriteArrayToTextStream(this, stream, options);
+  }
+
+  BufferType BackingStorage() const { return buffer_; }
+
+ private:
+  // This uses the same technique to select the correct definition of
+  // SizeOfBuffer() as in the SizeInBits()/SizeInBytes() selection above.
+  template <int N = 0>
+  typename ::std::enable_if<((void)N, kAddressableUnitSize == 8),
+                            ::std::size_t>::type
+  SizeOfBuffer() const {
+    return SizeInBytes();
+  }
+  template <int N = 0>
+  typename ::std::enable_if<((void)N, kAddressableUnitSize == 1),
+                            ::std::size_t>::type
+  SizeOfBuffer() const {
+    return SizeInBits();
+  }
+
+  // This mess is needed to expand the parameters_ tuple into individual
+  // arguments to the ElementView constructor.  If parameters_ has M elements,
+  // then:
+  //
+  // IndexOperatorHelper<false>::ConstructElement() calls
+  // IndexOperatorHelper<false, 0>::ConstructElement(), which calls
+  // IndexOperatorHelper<false, 0, 1>::ConstructElement(), and so on, up to
+  // IndexOperatorHelper<false, 0, 1, ..., M-1>::ConstructElement(), which calls
+  // IndexOperatorHelper<true, 0, 1, ..., M>::ConstructElement()
+  //
+  // That last call will resolve to the second, specialized version of
+  // IndexOperatorHelper.  That version's ConstructElement() uses
+  // `std::get<N>(parameters)...`, which will be expanded into
+  // `std::get<0>(parameters), std::get<1>(parameters), std::get<2>(parameters),
+  // ..., std::get<M>(parameters)`.
+  //
+  // If there are 0 parameters, then operator[]() will call
+  // IndexOperatorHelper<true>::ConstructElement(), which still works --
+  // `std::get<N>(parameters)...,` will be replaced by ``.
+  //
+  // In C++14, a lot of this can be replaced by std::index_sequence_of, and in
+  // C++17 it can be replaced with std::apply and a lambda.
+  //
+  // An alternate solution would be to force each parameterized view to have a
+  // constructor that accepts a tuple, instead of individual parameters, but
+  // that (further) complicates the matrix of constructors for view types.
+  template <bool, ::std::size_t... N>
+  struct IndexOperatorHelper {
+    static ElementView ConstructElement(
+        const ::std::tuple<ElementViewParameterTypes...> &parameters,
+        BufferType buffer, ::std::size_t index) {
+      return IndexOperatorHelper<
+          sizeof...(ElementViewParameterTypes) == 1 + sizeof...(N), N...,
+          sizeof...(N)>::ConstructElement(parameters, buffer, index);
+    }
+  };
+
+  template </**/ ::std::size_t... N>
+  struct IndexOperatorHelper<true, N...> {
+    static ElementView ConstructElement(
+        const ::std::tuple<ElementViewParameterTypes...> &parameters,
+        BufferType buffer, ::std::size_t index) {
+      return ElementView(::std::get<N>(parameters)...,
+                         buffer.template GetOffsetStorage<kElementSize, 0>(
+                             kElementSize * index, kElementSize));
+    }
+  };
+
+  ::std::tuple<ElementViewParameterTypes...> parameters_;
+  BufferType buffer_;
+};
+
+// Optionally prints a shorthand representation of a BitArray in a comment.
+template <class ElementView, class BufferType, size_t kElementSize,
+          size_t kAddressableUnitSize, class Stream>
+void WriteShorthandArrayCommentToTextStream(
+    const GenericArrayView<ElementView, BufferType, kElementSize,
+                           kAddressableUnitSize> *array,
+    Stream *stream, const TextOutputOptions &options) {
+  // Intentionally empty.  Overload for specific element types.
+}
+
+// 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,
+          size_t kElementSize, class Parameters,
+          class = typename ::std::enable_if<Parameters::kBits == 8>::type>
+void WriteShorthandArrayCommentToTextStream(
+    const GenericArrayView<prelude::UIntView<Parameters, BitViewType>,
+                           BufferType, kElementSize, 8> *array,
+    Stream *stream, const TextOutputOptions &options) {
+  WriteShorthandAsciiArrayCommentToTextStream(array, stream, options);
+}
+
+// Overload for arrays of UInt.
+// Prints out the elements as ASCII characters for arrays of Int:8.
+template <class BufferType, class BitViewType, class Stream,
+          size_t kElementSize, class Parameters,
+          class = typename ::std::enable_if<Parameters::kBits == 8>::type>
+void WriteShorthandArrayCommentToTextStream(
+    const GenericArrayView<prelude::IntView<Parameters, BitViewType>,
+                           BufferType, kElementSize, 8> *array,
+    Stream *stream, const TextOutputOptions &options) {
+  WriteShorthandAsciiArrayCommentToTextStream(array, stream, options);
+}
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_ARRAY_VIEW_H_
diff --git a/public/emboss_array_view_test.cc b/public/emboss_array_view_test.cc
new file mode 100644
index 0000000..1868d65
--- /dev/null
+++ b/public/emboss_array_view_test.cc
@@ -0,0 +1,280 @@
+// 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.
+
+#include "public/emboss_array_view.h"
+
+#include <string>
+#include <type_traits>
+
+#include "absl/strings/str_format.h"
+#include <gtest/gtest.h>
+
+#include "public/emboss_prelude.h"
+
+namespace emboss {
+namespace support {
+namespace test {
+
+using ::emboss::prelude::IntView;
+using ::emboss::prelude::UIntView;
+
+template <class ElementView, class BufferType, ::std::size_t kElementSize>
+using ArrayView = GenericArrayView<ElementView, BufferType, kElementSize, 8>;
+
+template <class ElementView, class BufferType, ::std::size_t kElementSize>
+using BitArrayView = GenericArrayView<ElementView, BufferType, kElementSize, 1>;
+
+template <size_t kBits>
+using LittleEndianBitBlockN =
+    BitBlock<LittleEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>;
+
+template <size_t kBits>
+using FixedUIntView = UIntView<FixedSizeViewParameters<kBits, AllValuesAreOk>,
+                               LittleEndianBitBlockN<kBits>>;
+
+template <size_t kBits>
+using FixedIntView = IntView<FixedSizeViewParameters<kBits, AllValuesAreOk>,
+                             LittleEndianBitBlockN<kBits>>;
+
+TEST(ArrayView, Methods) {
+  uint8_t bytes[] = {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09,
+                     0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
+  auto byte_array = ArrayView<FixedUIntView<8>, ReadWriteContiguousBuffer, 1>{
+      ReadWriteContiguousBuffer{bytes, sizeof bytes - 4}};
+  EXPECT_EQ(sizeof bytes - 4, byte_array.SizeInBytes());
+  EXPECT_EQ(bytes[0], byte_array[0].Read());
+  EXPECT_EQ(bytes[1], byte_array[1].Read());
+  EXPECT_EQ(bytes[2], byte_array[2].Read());
+  EXPECT_DEATH(byte_array[sizeof bytes - 4].Read(), "");
+  EXPECT_EQ(bytes[sizeof bytes - 4],
+            byte_array[sizeof bytes - 4].UncheckedRead());
+  EXPECT_TRUE(byte_array[sizeof bytes - 5].IsComplete());
+  EXPECT_FALSE(byte_array[sizeof bytes - 4].IsComplete());
+  EXPECT_TRUE(byte_array.Ok());
+  EXPECT_TRUE(byte_array.IsComplete());
+  EXPECT_FALSE((ArrayView<FixedUIntView<8>, ReadWriteContiguousBuffer, 1>{
+      ReadWriteContiguousBuffer{
+          nullptr}}.Ok()));
+  EXPECT_TRUE(byte_array.IsComplete());
+
+  auto uint32_array =
+      ArrayView<FixedUIntView<32>, ReadWriteContiguousBuffer, 4>{
+          ReadWriteContiguousBuffer{bytes, sizeof bytes - 4}};
+  EXPECT_EQ(sizeof bytes - 4, uint32_array.SizeInBytes());
+  EXPECT_TRUE(uint32_array[0].Ok());
+  EXPECT_EQ(0x0d0e0f10, uint32_array[0].Read());
+  EXPECT_EQ(0x090a0b0c, uint32_array[1].Read());
+  EXPECT_EQ(0x05060708, uint32_array[2].Read());
+  EXPECT_DEATH(uint32_array[3].Read(), "");
+  EXPECT_EQ(0x01020304, uint32_array[3].UncheckedRead());
+  EXPECT_TRUE(uint32_array[2].IsComplete());
+  EXPECT_FALSE(uint32_array[3].IsComplete());
+  EXPECT_TRUE(uint32_array.Ok());
+  EXPECT_TRUE(uint32_array.IsComplete());
+  EXPECT_FALSE((ArrayView<FixedUIntView<32>, ReadWriteContiguousBuffer, 1>{
+      ReadWriteContiguousBuffer{
+          nullptr}}.Ok()));
+}
+
+TEST(ArrayView, Ok) {
+  uint8_t bytes[] = {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09,
+                     0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
+  // All elements are complete and, themselves, Ok(), so the array should be
+  // Ok().
+  auto byte_array = ArrayView<FixedUIntView<16>, ReadWriteContiguousBuffer, 2>(
+      ReadWriteContiguousBuffer(bytes, sizeof bytes - 4));
+  EXPECT_TRUE(byte_array.Ok());
+
+  // An array with a partial element at the end should not be Ok().
+  byte_array = ArrayView<FixedUIntView<16>, ReadWriteContiguousBuffer, 2>(
+      ReadWriteContiguousBuffer(bytes, sizeof bytes - 3));
+  EXPECT_FALSE(byte_array.Ok());
+
+  // An empty array should be Ok().
+  byte_array = ArrayView<FixedUIntView<16>, ReadWriteContiguousBuffer, 2>(
+      ReadWriteContiguousBuffer(bytes, 0));
+  EXPECT_TRUE(byte_array.Ok());
+}
+
+TEST(ArrayView, TextFormatInput) {
+  uint8_t bytes[16] = {0};
+  auto byte_array = ArrayView<FixedUIntView<8>, ReadWriteContiguousBuffer, 1>{
+      ReadWriteContiguousBuffer{bytes, sizeof bytes}};
+  EXPECT_FALSE(UpdateFromText(byte_array, ""));
+  EXPECT_FALSE(UpdateFromText(byte_array, "[]"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{[0"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{[0:0}"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{[]:0}"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{[0] 0}"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{[0] 0}"));
+  EXPECT_TRUE(UpdateFromText(byte_array, "{}"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{,1}"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{1,,}"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{ a }"));
+  EXPECT_TRUE(UpdateFromText(byte_array, "{1}"));
+  EXPECT_EQ(1, bytes[0]);
+  EXPECT_TRUE(UpdateFromText(byte_array, " {2}"));
+  EXPECT_EQ(2, bytes[0]);
+  EXPECT_TRUE(UpdateFromText(byte_array, " {\t\r\n4  } junk"));
+  EXPECT_EQ(4, bytes[0]);
+  EXPECT_TRUE(UpdateFromText(byte_array, "{3,}"));
+  EXPECT_EQ(3, bytes[0]);
+  EXPECT_FALSE(UpdateFromText(byte_array, "{4 5}"));
+  EXPECT_TRUE(UpdateFromText(byte_array, "{4, 5}"));
+  EXPECT_EQ(4, bytes[0]);
+  EXPECT_EQ(5, bytes[1]);
+  EXPECT_TRUE(UpdateFromText(byte_array, "{5, [6]: 5}"));
+  EXPECT_EQ(5, bytes[0]);
+  EXPECT_EQ(5, bytes[1]);
+  EXPECT_EQ(5, bytes[6]);
+  EXPECT_TRUE(UpdateFromText(byte_array, "{6, [7]:6, 6}"));
+  EXPECT_EQ(6, bytes[0]);
+  EXPECT_EQ(5, bytes[1]);
+  EXPECT_EQ(5, bytes[6]);
+  EXPECT_EQ(6, bytes[7]);
+  EXPECT_EQ(6, bytes[8]);
+  EXPECT_TRUE(UpdateFromText(byte_array, "{[7]: 7, 7, [0]: 7, 7}"));
+  EXPECT_EQ(7, bytes[0]);
+  EXPECT_EQ(7, bytes[1]);
+  EXPECT_EQ(7, bytes[7]);
+  EXPECT_EQ(7, bytes[8]);
+  EXPECT_FALSE(UpdateFromText(byte_array, "{[16]: 0}"));
+  EXPECT_FALSE(UpdateFromText(byte_array, "{[15]: 0, 0}"));
+}
+
+TEST(ArrayView, TextFormatOutput_WithAndWithoutComments) {
+  signed char bytes[16] = {-3, 2, -1, 1,  0,  1,  1,  2,
+                           3,  5, 8,  13, 21, 34, 55, 89};
+  auto buffer = ReadWriteContiguousBuffer{reinterpret_cast<uint8_t *>(bytes),
+                                          sizeof bytes};
+  auto byte_array =
+      ArrayView<FixedIntView<8>, ReadWriteContiguousBuffer, 1>{buffer};
+  EXPECT_EQ(
+      "{ [0]: -3, 2, -1, 1, 0, 1, 1, 2, [8]: 3, 5, 8, 13, 21, 34, 55, 89 }",
+      WriteToString(byte_array));
+  EXPECT_EQ(WriteToString(byte_array, MultilineText()),
+            R"({
+  # ............."7Y
+  [0]: -3  # -0x3
+  [1]: 2  # 0x2
+  [2]: -1  # -0x1
+  [3]: 1  # 0x1
+  [4]: 0  # 0x0
+  [5]: 1  # 0x1
+  [6]: 1  # 0x1
+  [7]: 2  # 0x2
+  [8]: 3  # 0x3
+  [9]: 5  # 0x5
+  [10]: 8  # 0x8
+  [11]: 13  # 0xd
+  [12]: 21  # 0x15
+  [13]: 34  # 0x22
+  [14]: 55  # 0x37
+  [15]: 89  # 0x59
+})");
+  EXPECT_EQ(
+      WriteToString(byte_array,
+                    MultilineText().WithIndent("    ").WithComments(false)),
+      R"({
+    [0]: -3
+    [1]: 2
+    [2]: -1
+    [3]: 1
+    [4]: 0
+    [5]: 1
+    [6]: 1
+    [7]: 2
+    [8]: 3
+    [9]: 5
+    [10]: 8
+    [11]: 13
+    [12]: 21
+    [13]: 34
+    [14]: 55
+    [15]: 89
+})");
+  EXPECT_EQ(
+      WriteToString(byte_array, TextOutputOptions().WithNumericBase(16)),
+      "{ [0x0]: -0x3, 0x2, -0x1, 0x1, 0x0, 0x1, 0x1, 0x2, [0x8]: 0x3, 0x5, "
+      "0x8, 0xd, 0x15, 0x22, 0x37, 0x59 }");
+}
+
+TEST(ArrayView, TextFormatOutput_8BitIntElementTypes) {
+  uint8_t bytes[1] = {65};
+  auto buffer = ReadWriteContiguousBuffer{bytes, sizeof bytes};
+  const ::std::string expected_text = R"({
+  # A
+  [0]: 65  # 0x41
+})";
+  EXPECT_EQ(
+      WriteToString(
+          ArrayView<FixedIntView<8>, ReadWriteContiguousBuffer, 1>{buffer},
+          MultilineText()),
+      expected_text);
+  EXPECT_EQ(
+      WriteToString(
+          ArrayView<FixedUIntView<8>, ReadWriteContiguousBuffer, 1>{buffer},
+          MultilineText()),
+      expected_text);
+}
+
+TEST(ArrayView, TextFormatOutput_16BitIntElementTypes) {
+  uint16_t bytes[1] = {65};
+  auto buffer = ReadWriteContiguousBuffer{reinterpret_cast<uint8_t *>(bytes),
+                                          sizeof bytes};
+  const ::std::string expected_text = R"({
+  [0]: 65  # 0x41
+})";
+  EXPECT_EQ(
+      WriteToString(
+          ArrayView<FixedIntView<16>, ReadWriteContiguousBuffer, 2>{buffer},
+          MultilineText()),
+      expected_text);
+  EXPECT_EQ(
+      WriteToString(
+          ArrayView<FixedUIntView<16>, ReadWriteContiguousBuffer, 2>{buffer},
+          MultilineText()),
+      expected_text);
+}
+
+TEST(ArrayView, TextFormatOutput_MultilineComment) {
+  uint8_t bytes[65];
+  for (::std::size_t i = 0; i < sizeof bytes; ++i) {
+    bytes[i] = '0' + (i % 10);
+  }
+  for (const ::std::size_t length : {63, 64, 65}) {
+    auto buffer = ReadWriteContiguousBuffer{bytes, length};
+    ::std::string expected_text =
+        "{\n  # "
+        "012345678901234567890123456789012345678901234567890123456789012";
+    if (length > 63) expected_text += "3";
+    if (length > 64) expected_text += "\n  # 4";
+    expected_text += "\n";
+    for (::std::size_t i = 0; i < length; ++i) {
+      expected_text +=
+          absl::StrFormat("  [%d]: %d  # 0x%02x\n", i, bytes[i], bytes[i]);
+    }
+    expected_text += "}";
+    EXPECT_EQ(
+        WriteToString(
+            ArrayView<FixedIntView<8>, ReadWriteContiguousBuffer, 1>{buffer},
+            MultilineText()),
+        expected_text);
+  }
+}
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_bit_util.h b/public/emboss_bit_util.h
new file mode 100644
index 0000000..24ac132
--- /dev/null
+++ b/public/emboss_bit_util.h
@@ -0,0 +1,79 @@
+// 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.
+
+// This file contains various utility routines for manipulating values at a low
+// level, such as byte swaps and safe casts.
+#ifndef EMBOSS_PUBLIC_EMBOSS_BIT_UTIL_H_
+#define EMBOSS_PUBLIC_EMBOSS_BIT_UTIL_H_
+
+#include <cstdint>
+#include <type_traits>
+
+#include "public/emboss_defines.h"
+
+namespace emboss {
+namespace support {
+
+// Where possible, it is best to use byte swap builtins, but if they are not
+// available ByteSwap can fall back to portable code.
+inline constexpr ::std::uint8_t ByteSwap(::std::uint8_t x) { return x; }
+inline constexpr ::std::uint16_t ByteSwap(::std::uint16_t x) {
+#ifdef EMBOSS_BYTESWAP16
+  return EMBOSS_BYTESWAP16(x);
+#else
+  return (x << 8) | (x >> 8);
+#endif
+}
+inline constexpr ::std::uint32_t ByteSwap(::std::uint32_t x) {
+#ifdef EMBOSS_BYTESWAP32
+  return EMBOSS_BYTESWAP32(x);
+#else
+  return (static_cast</**/ ::std::uint32_t>(
+              ByteSwap(static_cast</**/ ::std::uint16_t>(x)))
+          << 16) |
+         ByteSwap(static_cast</**/ ::std::uint16_t>(x >> 16));
+#endif
+}
+inline constexpr ::std::uint64_t ByteSwap(::std::uint64_t x) {
+#ifdef EMBOSS_BYTESWAP64
+  return EMBOSS_BYTESWAP64(x);
+#else
+  return (static_cast</**/ ::std::uint64_t>(
+              ByteSwap(static_cast</**/ ::std::uint32_t>(x)))
+          << 32) |
+         ByteSwap(static_cast</**/ ::std::uint32_t>(x >> 32));
+#endif
+}
+
+// Masks the given value to the given number of bits.
+template <typename T>
+inline constexpr T MaskToNBits(T value, unsigned bits) {
+  static_assert(!::std::is_signed<T>::value,
+                "MaskToNBits only works on unsigned values.");
+  return bits < sizeof value * 8 ? value & ((static_cast<T>(1) << bits) - 1)
+                                 : value;
+}
+
+template <typename T>
+inline constexpr bool IsPowerOfTwo(T value) {
+  // This check relies on an old bit-counting trick; x & (x - 1) always has one
+  // fewer bit set to 1 than x (if x is nonzero), and powers of 2 always have
+  // exactly one 1 bit, thus x & (x - 1) == 0 if x is a power of 2.
+  return value > 0 && (value & (value - 1)) == 0;
+}
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_BIT_UTIL_H_
diff --git a/public/emboss_bit_util_test.cc b/public/emboss_bit_util_test.cc
new file mode 100644
index 0000000..8986383
--- /dev/null
+++ b/public/emboss_bit_util_test.cc
@@ -0,0 +1,184 @@
+// 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.
+
+#include "public/emboss_bit_util.h"
+
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+namespace test {
+
+TEST(ByteSwap, ByteSwap) {
+  EXPECT_EQ(0x01, ByteSwap(uint8_t{0x01}));
+  EXPECT_EQ(0x0102, ByteSwap(uint16_t{0x0201}));
+  EXPECT_EQ(0x01020304, ByteSwap(uint32_t{0x04030201}));
+  EXPECT_EQ(0x0102030405060708UL, ByteSwap(uint64_t{0x0807060504030201UL}));
+}
+
+TEST(MaskToNBits, MaskToNBits) {
+  EXPECT_EQ(0xff, MaskToNBits(0xffffffffU, 8));
+  EXPECT_EQ(0x00, MaskToNBits(0xffffff00U, 8));
+  EXPECT_EQ(0x01, MaskToNBits(0xffffffffU, 1));
+  EXPECT_EQ(0x00, MaskToNBits(0xfffffffeU, 1));
+  EXPECT_EQ(0xffffffffU, MaskToNBits(0xffffffffU, 32));
+  EXPECT_EQ(0xffffffffffffffffU, MaskToNBits(0xffffffffffffffffU, 64));
+  EXPECT_EQ(0xf, MaskToNBits(uint8_t{0xff}, 4));
+}
+
+TEST(IsPowerOfTwo, IsPowerOfTwo) {
+  EXPECT_TRUE(IsPowerOfTwo(1U));
+  EXPECT_TRUE(IsPowerOfTwo(2U));
+  EXPECT_TRUE(IsPowerOfTwo(1UL << 63));
+  EXPECT_TRUE(IsPowerOfTwo(uint8_t{128}));
+  EXPECT_TRUE(IsPowerOfTwo(1));
+  EXPECT_TRUE(IsPowerOfTwo(2));
+  EXPECT_TRUE(IsPowerOfTwo(1L << 62));
+  EXPECT_TRUE(IsPowerOfTwo(int8_t{64}));
+
+  EXPECT_FALSE(IsPowerOfTwo(0U));
+  EXPECT_FALSE(IsPowerOfTwo(3U));
+  EXPECT_FALSE(IsPowerOfTwo((1UL << 63) - 1));
+  EXPECT_FALSE(IsPowerOfTwo((1UL << 62) + 1));
+  EXPECT_FALSE(IsPowerOfTwo((3UL << 62)));
+  EXPECT_FALSE(IsPowerOfTwo(::std::numeric_limits<uint64_t>::max()));
+  EXPECT_FALSE(IsPowerOfTwo(uint8_t{129}));
+  EXPECT_FALSE(IsPowerOfTwo(uint8_t{255}));
+  EXPECT_FALSE(IsPowerOfTwo(-1));
+  EXPECT_FALSE(IsPowerOfTwo(-2));
+  EXPECT_FALSE(IsPowerOfTwo(-3));
+  EXPECT_FALSE(IsPowerOfTwo(::std::numeric_limits<int64_t>::min()));
+  EXPECT_FALSE(IsPowerOfTwo(::std::numeric_limits<int64_t>::max()));
+  EXPECT_FALSE(IsPowerOfTwo(0));
+  EXPECT_FALSE(IsPowerOfTwo(3));
+  EXPECT_FALSE(IsPowerOfTwo((1L << 62) - 1));
+  EXPECT_FALSE(IsPowerOfTwo((1L << 61) + 1));
+  EXPECT_FALSE(IsPowerOfTwo((3L << 61)));
+  EXPECT_FALSE(IsPowerOfTwo(int8_t{-1}));
+  EXPECT_FALSE(IsPowerOfTwo(int8_t{-128}));
+  EXPECT_FALSE(IsPowerOfTwo(int8_t{65}));
+  EXPECT_FALSE(IsPowerOfTwo(int8_t{127}));
+}
+
+#if defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE)
+TEST(EndianConversion, LittleEndianToNative) {
+  ::std::uint16_t data16 = 0;
+  reinterpret_cast<char *>(&data16)[0] = 0x01;
+  reinterpret_cast<char *>(&data16)[1] = 0x02;
+  EXPECT_EQ(0x0201, EMBOSS_LITTLE_ENDIAN_TO_NATIVE(data16));
+
+  ::std::uint32_t data32 = 0;
+  reinterpret_cast<char *>(&data32)[0] = 0x01;
+  reinterpret_cast<char *>(&data32)[1] = 0x02;
+  reinterpret_cast<char *>(&data32)[2] = 0x03;
+  reinterpret_cast<char *>(&data32)[3] = 0x04;
+  EXPECT_EQ(0x04030201, EMBOSS_LITTLE_ENDIAN_TO_NATIVE(data32));
+
+  ::std::uint64_t data64 = 0;
+  reinterpret_cast<char *>(&data64)[0] = 0x01;
+  reinterpret_cast<char *>(&data64)[1] = 0x02;
+  reinterpret_cast<char *>(&data64)[2] = 0x03;
+  reinterpret_cast<char *>(&data64)[3] = 0x04;
+  reinterpret_cast<char *>(&data64)[4] = 0x05;
+  reinterpret_cast<char *>(&data64)[5] = 0x06;
+  reinterpret_cast<char *>(&data64)[6] = 0x07;
+  reinterpret_cast<char *>(&data64)[7] = 0x08;
+  EXPECT_EQ(0x0807060504030201, EMBOSS_LITTLE_ENDIAN_TO_NATIVE(data64));
+}
+#endif  // defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE)
+
+#if defined(EMBOSS_BIG_ENDIAN_TO_NATIVE)
+TEST(EndianConversion, BigEndianToNative) {
+  ::std::uint16_t data16 = 0;
+  reinterpret_cast<char *>(&data16)[0] = 0x01;
+  reinterpret_cast<char *>(&data16)[1] = 0x02;
+  EXPECT_EQ(0x0102, EMBOSS_BIG_ENDIAN_TO_NATIVE(data16));
+
+  ::std::uint32_t data32 = 0;
+  reinterpret_cast<char *>(&data32)[0] = 0x01;
+  reinterpret_cast<char *>(&data32)[1] = 0x02;
+  reinterpret_cast<char *>(&data32)[2] = 0x03;
+  reinterpret_cast<char *>(&data32)[3] = 0x04;
+  EXPECT_EQ(0x01020304, EMBOSS_BIG_ENDIAN_TO_NATIVE(data32));
+
+  ::std::uint64_t data64 = 0;
+  reinterpret_cast<char *>(&data64)[0] = 0x01;
+  reinterpret_cast<char *>(&data64)[1] = 0x02;
+  reinterpret_cast<char *>(&data64)[2] = 0x03;
+  reinterpret_cast<char *>(&data64)[3] = 0x04;
+  reinterpret_cast<char *>(&data64)[4] = 0x05;
+  reinterpret_cast<char *>(&data64)[5] = 0x06;
+  reinterpret_cast<char *>(&data64)[6] = 0x07;
+  reinterpret_cast<char *>(&data64)[7] = 0x08;
+  EXPECT_EQ(0x0102030405060708, EMBOSS_BIG_ENDIAN_TO_NATIVE(data64));
+}
+#endif  // defined(EMBOSS_BIG_ENDIAN_TO_NATIVE)
+
+#if defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN)
+TEST(EndianConversion, NativeToLittleEndian) {
+  ::std::uint16_t data16 =
+      EMBOSS_NATIVE_TO_LITTLE_ENDIAN(static_cast</**/ ::std::uint16_t>(0x0201));
+  EXPECT_EQ(0x01, reinterpret_cast<char *>(&data16)[0]);
+  EXPECT_EQ(0x02, reinterpret_cast<char *>(&data16)[1]);
+
+  ::std::uint32_t data32 = EMBOSS_NATIVE_TO_LITTLE_ENDIAN(
+      static_cast</**/ ::std::uint32_t>(0x04030201));
+  EXPECT_EQ(0x01, reinterpret_cast<char *>(&data32)[0]);
+  EXPECT_EQ(0x02, reinterpret_cast<char *>(&data32)[1]);
+  EXPECT_EQ(0x03, reinterpret_cast<char *>(&data32)[2]);
+  EXPECT_EQ(0x04, reinterpret_cast<char *>(&data32)[3]);
+
+  ::std::uint64_t data64 = EMBOSS_NATIVE_TO_LITTLE_ENDIAN(
+      static_cast</**/ ::std::uint64_t>(0x0807060504030201));
+  EXPECT_EQ(0x01, reinterpret_cast<char *>(&data64)[0]);
+  EXPECT_EQ(0x02, reinterpret_cast<char *>(&data64)[1]);
+  EXPECT_EQ(0x03, reinterpret_cast<char *>(&data64)[2]);
+  EXPECT_EQ(0x04, reinterpret_cast<char *>(&data64)[3]);
+  EXPECT_EQ(0x05, reinterpret_cast<char *>(&data64)[4]);
+  EXPECT_EQ(0x06, reinterpret_cast<char *>(&data64)[5]);
+  EXPECT_EQ(0x07, reinterpret_cast<char *>(&data64)[6]);
+  EXPECT_EQ(0x08, reinterpret_cast<char *>(&data64)[7]);
+}
+#endif  // defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN)
+
+#if defined(EMBOSS_NATIVE_TO_BIG_ENDIAN)
+TEST(EndianConversion, NativeToBigEndian) {
+  ::std::uint16_t data16 =
+      EMBOSS_NATIVE_TO_BIG_ENDIAN(static_cast</**/ ::std::uint16_t>(0x0102));
+  EXPECT_EQ(0x01, reinterpret_cast<char *>(&data16)[0]);
+  EXPECT_EQ(0x02, reinterpret_cast<char *>(&data16)[1]);
+
+  ::std::uint32_t data32 = EMBOSS_NATIVE_TO_BIG_ENDIAN(
+      static_cast</**/ ::std::uint32_t>(0x01020304));
+  EXPECT_EQ(0x01, reinterpret_cast<char *>(&data32)[0]);
+  EXPECT_EQ(0x02, reinterpret_cast<char *>(&data32)[1]);
+  EXPECT_EQ(0x03, reinterpret_cast<char *>(&data32)[2]);
+  EXPECT_EQ(0x04, reinterpret_cast<char *>(&data32)[3]);
+
+  ::std::uint64_t data64 = EMBOSS_NATIVE_TO_BIG_ENDIAN(
+      static_cast</**/ ::std::uint64_t>(0x0102030405060708));
+  EXPECT_EQ(0x01, reinterpret_cast<char *>(&data64)[0]);
+  EXPECT_EQ(0x02, reinterpret_cast<char *>(&data64)[1]);
+  EXPECT_EQ(0x03, reinterpret_cast<char *>(&data64)[2]);
+  EXPECT_EQ(0x04, reinterpret_cast<char *>(&data64)[3]);
+  EXPECT_EQ(0x05, reinterpret_cast<char *>(&data64)[4]);
+  EXPECT_EQ(0x06, reinterpret_cast<char *>(&data64)[5]);
+  EXPECT_EQ(0x07, reinterpret_cast<char *>(&data64)[6]);
+  EXPECT_EQ(0x08, reinterpret_cast<char *>(&data64)[7]);
+}
+#endif  // defined(EMBOSS_NATIVE_TO_BIG_ENDIAN)
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_constant_view.h b/public/emboss_constant_view.h
new file mode 100644
index 0000000..41785e5
--- /dev/null
+++ b/public/emboss_constant_view.h
@@ -0,0 +1,51 @@
+// 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.
+
+#ifndef EMBOSS_PUBLIC_EMBOSS_CONSTANT_VIEW_H_
+#define EMBOSS_PUBLIC_EMBOSS_CONSTANT_VIEW_H_
+
+#include "public/emboss_maybe.h"
+
+namespace emboss {
+namespace support {
+
+// MaybeConstantView is a "view" type that "reads" a value passed into its
+// constructor.
+//
+// This is used internally by generated structure view classes to provide views
+// of parameters; in this way, parameters can be treated like fields in the
+// generated code.
+template <typename ValueT>
+class MaybeConstantView {
+ public:
+  MaybeConstantView() : value_() {}
+  constexpr explicit MaybeConstantView(ValueT value) : value_(value) {}
+  MaybeConstantView(const MaybeConstantView &) = default;
+  MaybeConstantView(MaybeConstantView &&) = default;
+  MaybeConstantView &operator=(const MaybeConstantView &) = default;
+  MaybeConstantView &operator=(MaybeConstantView &&) = default;
+  ~MaybeConstantView() = default;
+
+  constexpr ValueT Read() const { return value_.Value(); }
+  constexpr ValueT UncheckedRead() const { return value_.ValueOrDefault(); }
+  constexpr bool Ok() { return value_.Known(); }
+
+ private:
+  ::emboss::support::Maybe<ValueT> value_;
+};
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_CONSTANT_VIEW_H_
diff --git a/public/emboss_constant_view_test.cc b/public/emboss_constant_view_test.cc
new file mode 100644
index 0000000..aafc14e
--- /dev/null
+++ b/public/emboss_constant_view_test.cc
@@ -0,0 +1,62 @@
+// 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.
+
+#include "public/emboss_constant_view.h"
+
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+namespace test {
+
+TEST(MaybeConstantViewTest, Read) {
+  EXPECT_EQ(7, MaybeConstantView</**/ ::std::uint8_t>(7).Read());
+  EXPECT_DEATH(MaybeConstantView</**/ ::std::uint8_t>().Read(), "Known\\(\\)");
+}
+
+TEST(MaybeConstantViewTest, UncheckedRead) {
+  EXPECT_EQ(7, MaybeConstantView</**/ ::std::uint8_t>(7).UncheckedRead());
+  EXPECT_EQ(0, MaybeConstantView</**/ ::std::uint8_t>().UncheckedRead());
+}
+
+TEST(MaybeConstantViewTest, Ok) {
+  EXPECT_TRUE(MaybeConstantView</**/ ::std::uint8_t>(7).Ok());
+  EXPECT_FALSE(MaybeConstantView</**/ ::std::uint8_t>().Ok());
+}
+
+TEST(MaybeConstantViewTest, CopyConstruction) {
+  auto with_value = MaybeConstantView</**/ ::std::uint8_t>(7);
+  auto copied_with_value = with_value;
+  EXPECT_EQ(7, copied_with_value.Read());
+
+  auto without_value = MaybeConstantView</**/ ::std::uint8_t>();
+  auto copied_without_value = without_value;
+  EXPECT_FALSE(copied_without_value.Ok());
+}
+
+TEST(MaybeConstantViewTest, Assignment) {
+  auto with_value = MaybeConstantView</**/ ::std::uint8_t>(7);
+  MaybeConstantView</**/ ::std::uint8_t> copied_with_value;
+  copied_with_value = with_value;
+  EXPECT_EQ(7, copied_with_value.Read());
+
+  auto without_value = MaybeConstantView</**/ ::std::uint8_t>();
+  MaybeConstantView</**/ ::std::uint8_t> copied_without_value;
+  copied_without_value = without_value;
+  EXPECT_FALSE(copied_without_value.Ok());
+}
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_cpp_types.h b/public/emboss_cpp_types.h
new file mode 100644
index 0000000..a4aa3b2
--- /dev/null
+++ b/public/emboss_cpp_types.h
@@ -0,0 +1,125 @@
+// 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.
+
+// This file contains various C++ type aliases for use in Emboss.
+#ifndef EMBOSS_PUBLIC_EMBOSS_CPP_TYPES_H_
+#define EMBOSS_PUBLIC_EMBOSS_CPP_TYPES_H_
+
+#include <climits>
+#include <cstdint>
+#include <type_traits>
+
+namespace emboss {
+namespace support {
+
+static_assert(sizeof(long long) * CHAR_BIT >= 64,  // NOLINT
+              "Emboss requires that long long is at least 64 bits.");
+
+// FloatType<n_bits>::Type is the C++ floating-point type of the appropriate
+// size.
+template <int kBits>
+struct FloatType final {
+  static_assert(kBits == 32 || kBits == 64, "Unknown floating-point size.");
+};
+template <>
+struct FloatType<64> final {
+  static_assert(sizeof(double) * CHAR_BIT == 64,
+                "C++ double type must be 64 bits!");
+  using Type = double;
+  using UIntType = ::std::uint64_t;
+};
+template <>
+struct FloatType<32> final {
+  static_assert(sizeof(float) * CHAR_BIT == 32,
+                "C++ float type must be 32 bits!");
+  using Type = float;
+  using UIntType = ::std::uint32_t;
+};
+
+// LeastWidthInteger<n_bits>::Unsigned is the smallest uintNN_t type that can
+// hold n_bits or more.  LeastWidthInteger<n_bits>::Signed is the corresponding
+// signed type.
+template <int kBits>
+struct LeastWidthInteger final {
+  static_assert(kBits <= 64, "Only bit sizes up to 64 are supported.");
+  using Unsigned = typename LeastWidthInteger<kBits + 1>::Unsigned;
+  using Signed = typename LeastWidthInteger<kBits + 1>::Signed;
+};
+template <>
+struct LeastWidthInteger<64> final {
+  using Unsigned = ::std::uint64_t;
+  using Signed = ::std::int64_t;
+};
+template <>
+struct LeastWidthInteger<32> final {
+  using Unsigned = ::std::uint32_t;
+  using Signed = ::std::int32_t;
+};
+template <>
+struct LeastWidthInteger<16> final {
+  using Unsigned = ::std::uint16_t;
+  using Signed = ::std::int16_t;
+};
+template <>
+struct LeastWidthInteger<8> final {
+  using Unsigned = ::std::uint8_t;
+  using Signed = ::std::int8_t;
+};
+
+// IsChar<T>::value is true if T is a character type; i.e. const? volatile?
+// (signed|unsigned)? char.
+template <typename T>
+struct IsChar {
+  // Note that 'char' is a distinct type from 'signed char' and 'unsigned char'.
+  static constexpr bool value =
+      ::std::is_same<char, typename ::std::remove_cv<T>::type>::value ||
+      ::std::is_same<unsigned char,
+                     typename ::std::remove_cv<T>::type>::value ||
+      ::std::is_same<signed char, typename ::std::remove_cv<T>::type>::value;
+};
+
+// The static member variable requires a definition.
+template <typename T>
+constexpr bool IsChar<T>::value;
+
+// AddSourceConst<SourceT, DestT>::Type is DestT's base type with const added if
+// SourceT is const.
+template <typename SourceT, typename DestT>
+struct AddSourceConst {
+  using Type = typename ::std::conditional<
+      /**/ ::std::is_const<SourceT>::value,
+      typename ::std::add_const<DestT>::type, DestT>::type;
+};
+
+// AddSourceVolatile<SourceT, DestT>::Type is DestT's base type with volatile
+// added if SourceT is volatile.
+template <typename SourceT, typename DestT>
+struct AddSourceVolatile {
+  using Type = typename ::std::conditional<
+      /**/ ::std::is_volatile<SourceT>::value,
+      typename ::std::add_volatile<DestT>::type, DestT>::type;
+};
+
+// AddCV<SourceT, DestT>::Type is DestT's base type with SourceT's const and
+// volatile qualifiers added, if any.
+template <typename SourceT, typename DestT>
+struct AddSourceCV {
+  using Type = typename AddSourceConst<
+      SourceT, typename AddSourceVolatile<SourceT, DestT>::Type>::Type;
+};
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_CPP_TYPES_H_
diff --git a/public/emboss_cpp_types_test.cc b/public/emboss_cpp_types_test.cc
new file mode 100644
index 0000000..e56858a
--- /dev/null
+++ b/public/emboss_cpp_types_test.cc
@@ -0,0 +1,199 @@
+// 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.
+
+#include "public/emboss_cpp_types.h"
+
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+namespace test {
+
+TEST(FloatTypes, Types) {
+  EXPECT_EQ(32 / CHAR_BIT, sizeof(FloatType<32>::Type));
+  EXPECT_EQ(64 / CHAR_BIT, sizeof(FloatType<64>::Type));
+  EXPECT_EQ(32 / CHAR_BIT, sizeof(FloatType<32>::UIntType));
+  EXPECT_EQ(64 / CHAR_BIT, sizeof(FloatType<64>::UIntType));
+  EXPECT_TRUE(::std::is_floating_point<FloatType<32>::Type>::value);
+  EXPECT_TRUE(::std::is_floating_point<FloatType<64>::Type>::value);
+  EXPECT_TRUE(
+      (::std::is_same<FloatType<32>::UIntType, ::std::uint32_t>::value));
+  EXPECT_TRUE(
+      (::std::is_same<FloatType<64>::UIntType, ::std::uint64_t>::value));
+}
+
+TEST(LeastWidthInteger, Types) {
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<1>::Unsigned, ::std::uint8_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<1>::Signed, ::std::int8_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<4>::Unsigned, ::std::uint8_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<4>::Signed, ::std::int8_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<7>::Unsigned, ::std::uint8_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<7>::Signed, ::std::int8_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<8>::Unsigned, ::std::uint8_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<8>::Signed, ::std::int8_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<9>::Unsigned, ::std::uint16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<9>::Signed, ::std::int16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<12>::Unsigned, ::std::uint16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<12>::Signed, ::std::int16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<15>::Unsigned, ::std::uint16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<15>::Signed, ::std::int16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<16>::Unsigned, ::std::uint16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<16>::Signed, ::std::int16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<17>::Unsigned, ::std::uint32_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<17>::Signed, ::std::int32_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<28>::Unsigned, ::std::uint32_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<28>::Signed, ::std::int32_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<31>::Unsigned, ::std::uint32_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<31>::Signed, ::std::int32_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<32>::Unsigned, ::std::uint32_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<32>::Signed, ::std::int32_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<33>::Unsigned, ::std::uint64_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<33>::Signed, ::std::int64_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<60>::Unsigned, ::std::uint64_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<60>::Signed, ::std::int64_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<63>::Unsigned, ::std::uint64_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<63>::Signed, ::std::int64_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<64>::Unsigned, ::std::uint64_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<LeastWidthInteger<64>::Signed, ::std::int64_t>::value));
+}
+
+TEST(IsChar, CharTypes) {
+  EXPECT_TRUE(IsChar<char>::value);
+  EXPECT_TRUE(IsChar<unsigned char>::value);
+  EXPECT_TRUE(IsChar<signed char>::value);
+  EXPECT_TRUE(IsChar<const char>::value);
+  EXPECT_TRUE(IsChar<const unsigned char>::value);
+  EXPECT_TRUE(IsChar<const signed char>::value);
+  EXPECT_TRUE(IsChar<volatile char>::value);
+  EXPECT_TRUE(IsChar<volatile unsigned char>::value);
+  EXPECT_TRUE(IsChar<volatile signed char>::value);
+  EXPECT_TRUE(IsChar<const volatile char>::value);
+  EXPECT_TRUE(IsChar<const volatile unsigned char>::value);
+  EXPECT_TRUE(IsChar<const volatile signed char>::value);
+}
+
+TEST(IsChar, NonCharTypes) {
+  struct OneByte { char c; };
+  EXPECT_EQ(1, sizeof(OneByte));
+  EXPECT_FALSE(IsChar<int>::value);
+  EXPECT_FALSE(IsChar<unsigned>::value);
+  EXPECT_FALSE(IsChar<const int>::value);
+  EXPECT_FALSE(IsChar<OneByte>::value);
+}
+
+TEST(AddSourceConst, AddSourceConst) {
+  EXPECT_TRUE(
+      (::std::is_same<const char,
+                      typename AddSourceConst<const int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<
+          const volatile char,
+          typename AddSourceConst<const int, volatile char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<char, typename AddSourceConst<int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<
+          char, typename AddSourceConst<volatile int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<const char,
+                      typename AddSourceConst<int, const char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<const char, typename AddSourceConst<
+                                      const int, const char>::Type>::value));
+}
+
+TEST(AddSourceVolatile, AddSourceVolatile) {
+  EXPECT_TRUE(
+      (::std::is_same<volatile char, typename AddSourceVolatile<
+                                         volatile int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<
+          const volatile char,
+          typename AddSourceVolatile<volatile int, const char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<char,
+                      typename AddSourceVolatile<int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<
+          char, typename AddSourceVolatile<const int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<volatile char, typename AddSourceVolatile<
+                                         int, volatile char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<volatile char,
+                      typename AddSourceVolatile<volatile int,
+                                                 volatile char>::Type>::value));
+}
+
+TEST(AddSourceCV, AddSourceCV) {
+  EXPECT_TRUE(
+      (::std::is_same<const char,
+                      typename AddSourceCV<const int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<volatile char,
+                      typename AddSourceCV<volatile int, char>::Type>::value));
+  EXPECT_TRUE((::std::is_same<
+               const volatile char,
+               typename AddSourceCV<volatile int, const char>::Type>::value));
+  EXPECT_TRUE((::std::is_same<
+               const volatile char,
+               typename AddSourceCV<const int, volatile char>::Type>::value));
+  EXPECT_TRUE((::std::is_same<
+               const volatile char,
+               typename AddSourceCV<const volatile int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<char, typename AddSourceCV<int, char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<volatile char,
+                      typename AddSourceCV<int, volatile char>::Type>::value));
+  EXPECT_TRUE(
+      (::std::is_same<
+          volatile char,
+          typename AddSourceCV<volatile int, volatile char>::Type>::value));
+}
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_cpp_util.h b/public/emboss_cpp_util.h
new file mode 100644
index 0000000..624f931
--- /dev/null
+++ b/public/emboss_cpp_util.h
@@ -0,0 +1,31 @@
+// 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.
+
+// This header exports utilities that are needed for all Emboss-generated C++
+// code.
+#ifndef EMBOSS_PUBLIC_EMBOSS_CPP_UTIL_H_
+#define EMBOSS_PUBLIC_EMBOSS_CPP_UTIL_H_
+
+#include "public/emboss_arithmetic.h"
+#include "public/emboss_array_view.h"
+#include "public/emboss_bit_util.h"
+#include "public/emboss_constant_view.h"
+#include "public/emboss_cpp_types.h"
+#include "public/emboss_defines.h"
+#include "public/emboss_enum_view.h"
+#include "public/emboss_memory_util.h"
+#include "public/emboss_text_util.h"
+#include "public/emboss_view_parameters.h"
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_CPP_UTIL_H_
diff --git a/public/emboss_cpp_util_google_integration_test.cc b/public/emboss_cpp_util_google_integration_test.cc
new file mode 100644
index 0000000..c634b10
--- /dev/null
+++ b/public/emboss_cpp_util_google_integration_test.cc
@@ -0,0 +1,37 @@
+// 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.
+
+#include "public/emboss_cpp_util.h"
+
+#include <gtest/gtest.h>
+#include "third_party/absl/strings/string_view.h"
+
+namespace emboss {
+namespace support {
+namespace test {
+
+TEST(TextStream, Construction) {
+  absl::string_view view_text = "gh";
+  auto text_stream = TextStream(view_text);
+  char result;
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('g', result);
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('h', result);
+  EXPECT_FALSE(text_stream.Read(&result));
+}
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_cpp_util_nc.cc b/public/emboss_cpp_util_nc.cc
new file mode 100644
index 0000000..7e72db7
--- /dev/null
+++ b/public/emboss_cpp_util_nc.cc
@@ -0,0 +1,43 @@
+// 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.
+
+#include "public/emboss_cpp_util.h"
+
+#include <string>
+
+namespace emboss {
+namespace {
+
+void X() {
+#ifdef TEST_CANNOT_CONSTRUCT_READ_WRITE_CONTIGUOUS_BUFFER_FROM_STRING
+  ::std::string foo = "string";
+
+  // Read-only ContiguousBuffer should be fine.
+  (void)ContiguousBuffer<const char>(foo);
+
+  // Read-write ContiguousBuffer should be fail.
+  (void)ContiguousBuffer<char>(foo);
+#endif  // TEST_CANNOT_CONSTRUCT_READ_WRITE_CONTIGUOUS_BUFFER_FROM_STRING
+
+#ifdef TEST_CANNOT_CONSTRUCT_NON_BYTE_CONTIGUOUS_BUFFER
+  // ContiguousBuffer<char>(nullptr) should be fine...
+  (void)ContiguousBuffer<char>(nullptr);
+
+  // ... but ContiguousBuffer<int>(nullptr) should not.
+  (void)ContiguousBuffer<int>(nullptr);
+#endif  // TEST_CANNOT_CONSTRUCT_NON_BYTE_CONTIGUOUS_BUFFER
+}
+
+}  // namespace
+}  // namespace emboss
diff --git a/public/emboss_defines.h b/public/emboss_defines.h
new file mode 100644
index 0000000..3be4d66
--- /dev/null
+++ b/public/emboss_defines.h
@@ -0,0 +1,167 @@
+// 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.
+
+// This header contains #defines that are used to control Emboss's generated
+// code.
+#ifndef EMBOSS_PUBLIC_EMBOSS_DEFINES_H_
+#define EMBOSS_PUBLIC_EMBOSS_DEFINES_H_
+
+// TODO(bolms): Add an explicit extension point for these macros.
+#include <assert.h>
+#define EMBOSS_CHECK(x) assert((x))
+#define EMBOSS_CHECK_LE(x, y) assert((x) <= (y))
+#define EMBOSS_CHECK_GE(x, y) assert((x) >= (y))
+#define EMBOSS_CHECK_GT(x, y) assert((x) > (y))
+#define EMBOSS_CHECK_EQ(x, y) assert((x) == (y))
+
+// If EMBOSS_FORCE_ALL_CHECKS is #defined, then all checks are enabled even in
+// optimized modes.  Otherwise, EMBOSS_DCHECK only runs in debug mode.
+#ifdef EMBOSS_FORCE_ALL_CHECKS
+#define EMBOSS_DCHECK_EQ(x, y) assert((x) == (y))
+#define EMBOSS_DCHECK_GE(x, y) assert((x) >= (y))
+#else
+#define EMBOSS_DCHECK_EQ(x, y) assert((x) == (y))
+#define EMBOSS_DCHECK_GE(x, y) assert((x) >= (y))
+#endif  // EMBOSS_FORCE_ALL_CHECKS
+
+// Technically, the mapping from pointers to integers is implementation-defined,
+// but the standard states "[ Note: It is intended to be unsurprising to those
+// who know the addressing structure of the underlying machine. - end note ],"
+// so this should be a reasonably safe way to check that a pointer is aligned.
+#define EMBOSS_DCHECK_POINTER_ALIGNMENT(p, align, offset)                  \
+  EMBOSS_DCHECK_EQ(reinterpret_cast</**/ ::std::uintptr_t>((p)) % (align), \
+                   (offset))
+#define EMBOSS_CHECK_POINTER_ALIGNMENT(p, align, offset)                  \
+  EMBOSS_CHECK_EQ(reinterpret_cast</**/ ::std::uintptr_t>((p)) % (align), \
+                  (offset))
+
+// !! WARNING !!
+//
+// It is possible to pre-#define a number of macros used below to influence
+// Emboss's system-specific optimizations.  If so, they *must* be #defined the
+// same way in every compilation unit that #includes any Emboss-related header
+// or generated code, before any such headers are #included, or else there is a
+// real risk of ODR violations.  It is recommended that any EMBOSS_* #defines
+// are added to the global C++ compiler options in your build system, rather
+// than being individually specified in source files.
+//
+// TODO(bolms): Add an #include for a site-specific header file, where
+// site-specific customizations can be placed, and recommend that any overrides
+// be placed in that header.  Further, use that header for the EMBOSS_CHECK_*
+// #defines, above.
+
+// EMBOSS_NO_OPTIMIZATIONS is used to turn off all system-specific
+// optimizations.  This is mostly intended for testing, but could be used if
+// optimizations are causing problems.
+#if !defined(EMBOSS_NO_OPTIMIZATIONS)
+#if defined(__GNUC__)  // GCC and "compatible" compilers, such as Clang.
+
+// GCC, Clang, and ICC only support two's-complement systems, so it is safe to
+// assume two's-complement for those systems.  In particular, this means that
+// static_cast<int>() will treat its argument as a two's-complement bit pattern,
+// which means that it is reasonable to static_cast<int>(some_unsigned_value).
+//
+// TODO(bolms): Are there actually any non-archaic systems that use any integer
+// types other than 2's-complement?
+#ifndef EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT
+#define EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT 1
+#endif
+
+#if !defined(__INTEL_COMPILER)
+// On systems with known host byte order, Emboss can always use memcpy to safely
+// and relatively efficiently read and write values from and to memory.
+// However, memcpy cannot assume that its pointers are aligned.  On common
+// platforms, particularly x86, this almost never matters; however, on some
+// systems this can add considerable overhead, as memcpy must either perform
+// byte-by-byte copies or perform tests to determine pointer alignment and then
+// dispatch to alignment-specific code.
+//
+// Platforms with no alignment restrictions:
+//
+// * x86 (except for a few SSE instructions like movdqa: see
+//   http://pzemtsov.github.io/2016/11/06/bug-story-alignment-on-x86.html)
+// * ARM systems with ARMv6 and later ISAs
+// * High-end POWER-based systems
+// * POWER-based systems with an alignment exception handler installed (but note
+//   that emulated unaligned reads are *very* slow)
+//
+// Platforms with alignment restrictions:
+//
+// * MicroBlaze
+// * Emscripten
+// * Low-end bare-metal POWER-based systems
+// * ARM systems with ARMv5 and earlier ISAs
+// * x86 with the AC bit of EEFLAGS enabled (but note that this is never enabled
+//   on any normal system, and, e.g., you will get crashes in glibc if you try
+//   to enable it)
+//
+// The naive solution is to reinterpret_cast to a type like uint32_t, then read
+// or write through that pointer; however, this can easily run afoul of C++'s
+// type aliasing rules and result in undefined behavior.
+//
+// On GCC, there is a solution to this: use the "__may_alias__" type attribute,
+// which essentially forces the type to have the same aliasing rules as char;
+// i.e., it is safe to read and write through a pointer derived from
+// reinterpret_cast<T __attribute__((__may_alias__)) *>, just as it is safe to
+// read and write through a pointer derived from reinterpret_cast<char *>.
+//
+// Note that even though ICC pretends to be compatible with GCC by defining
+// __GNUC__, it does *not* appear to support the __may_alias__ attribute.
+// (TODO(bolms): verify this if/when Emboss explicitly supports ICC.)
+//
+// Note the lack of parentheses around 't' in the expansion: unfortunately,
+// GCC's attribute syntax disallows parentheses in that particular position.
+#define EMBOSS_ALIAS_SAFE_POINTER_CAST(t, x) \
+  reinterpret_cast<t __attribute__((__may_alias__)) *>((x))
+#endif  // !defined(__INTEL_COMPILER)
+
+// GCC supports __BYTE_ORDER__ of __ORDER_LITTLE_ENDIAN__, __ORDER_BIG_ENDIAN__,
+// and __ORDER_PDP_ENDIAN__.  Since all available test systems are
+// __ORDER_LITTLE_ENDIAN__, only little-endian hosts get optimized code paths;
+// however, big-endian support ought to be trivial to add.
+//
+// There are no plans to support PDP-endian systems.
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+// EMBOSS_LITTLE_ENDIAN_TO_NATIVE and EMBOSS_BIG_ENDIAN_TO_NATIVE can be used to
+// fix up integers after a little- or big-endian value has been memcpy'ed into
+// them.
+//
+// On little-endian systems, no fixup is needed for little-endian sources, but
+// big-endian sources require a byte swap.
+#define EMBOSS_LITTLE_ENDIAN_TO_NATIVE(x) (x)
+#define EMBOSS_NATIVE_TO_LITTLE_ENDIAN(x) (x)
+#define EMBOSS_BIG_ENDIAN_TO_NATIVE(x) (::emboss::support::ByteSwap((x)))
+#define EMBOSS_NATIVE_TO_BIG_ENDIAN(x) (::emboss::support::ByteSwap((x)))
+// TODO(bolms): Find a way to test on a big-endian architecture, and add support
+// for __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#endif  // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+
+// Prior to version 4.8, __builtin_bswap16 was not available on all platforms.
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52624
+//
+// Clang pretends to be an earlier GCC, but does support __builtin_bswap16.
+// Clang recommends using __has_builtin(__builtin_bswap16), but unfortunately
+// that fails to compile on GCC, even with defined(__has_builtin) &&
+// __has_builtin(__builtin_bswap16), so instead Emboss just checks for
+// defined(__clang__).
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || defined(__clang__)
+#define EMBOSS_BYTESWAP16(x) __builtin_bswap16((x))
+#endif  // __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
+#define EMBOSS_BYTESWAP32(x) __builtin_bswap32((x))
+#define EMBOSS_BYTESWAP64(x) __builtin_bswap64((x))
+
+#endif  // defined(__GNUC__)
+#endif  // !defined(EMBOSS_NO_OPTIMIZATIONS)
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_DEFINES_H_
diff --git a/public/emboss_defines_test.cc b/public/emboss_defines_test.cc
new file mode 100644
index 0000000..198b0c4
--- /dev/null
+++ b/public/emboss_defines_test.cc
@@ -0,0 +1,59 @@
+// 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.
+
+#include "public/emboss_defines.h"
+
+#include <cstdint>
+
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+namespace test {
+
+TEST(CheckPointerAlignment, Aligned) {
+  ::std::uint32_t t;
+  EMBOSS_DCHECK_POINTER_ALIGNMENT(&t, sizeof t, 0);
+  EMBOSS_DCHECK_POINTER_ALIGNMENT(&t, 1, 0);
+  EMBOSS_DCHECK_POINTER_ALIGNMENT(reinterpret_cast<char *>(&t) + 1, sizeof t,
+                                  1);
+  EMBOSS_DCHECK_POINTER_ALIGNMENT(reinterpret_cast<char *>(&t) + 1, 1, 0);
+}
+
+TEST(CheckPointerAlignment, Misaligned) {
+  ::std::uint32_t t;
+  EXPECT_DEATH(EMBOSS_DCHECK_POINTER_ALIGNMENT(&t, sizeof t, 1), "");
+  EXPECT_DEATH(EMBOSS_DCHECK_POINTER_ALIGNMENT(reinterpret_cast<char *>(&t) + 1,
+                                               sizeof t, 0),
+               "");
+}
+
+#if EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT
+TEST(SystemIsTwosComplement, CastToSigned) {
+  EXPECT_EQ(-static_cast</**/ ::std::int64_t>(0x80000000),
+            static_cast</**/ ::std::int32_t>(0x80000000));
+}
+#endif  // EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT
+
+// Note: I (bolms@) can't think of a way to truly test
+// EMBOSS_ALIAS_SAFE_POINTER_CAST, since the compiler might let it work even if
+// it's not "supposed" to.  (E.g., even with -fstrict-aliasing, GCC doesn't
+// always take advantage of strict aliasing to do any optimizations.)
+
+// The native <=> fixed endian macros are tested in emboss_bit_util_test.cc,
+// since their expansions rely on emboss_bit_util.h.
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_enum_view.h b/public/emboss_enum_view.h
new file mode 100644
index 0000000..6ed9453
--- /dev/null
+++ b/public/emboss_enum_view.h
@@ -0,0 +1,159 @@
+// 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.
+
+// View class template for enums.
+#ifndef EMBOSS_PUBLIC_EMBOSS_ENUM_VIEW_H_
+#define EMBOSS_PUBLIC_EMBOSS_ENUM_VIEW_H_
+
+#include <cctype>
+#include <cstdint>
+#include <string>
+#include <utility>
+
+#include "public/emboss_text_util.h"
+#include "public/emboss_view_parameters.h"
+
+namespace emboss {
+namespace support {
+
+// EnumView is a view for Enums inside of bitfields.
+template <class Enum, class Parameters, class BitViewType>
+class EnumView final {
+ public:
+  using ValueType = typename ::std::remove_cv<Enum>::type;
+  static_assert(
+      Parameters::kBits <= sizeof(ValueType) * 8,
+      "EnumView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
+  template <typename... Args>
+  explicit EnumView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+  EnumView() : buffer_() {}
+  EnumView(const EnumView &) = default;
+  EnumView(EnumView &&) = default;
+  EnumView &operator=(const EnumView &) = default;
+  EnumView &operator=(EnumView &&) = default;
+  ~EnumView() = default;
+
+  // TODO(bolms): Here and in CouldWriteValue(), the static_casts to ValueType
+  // rely on implementation-defined behavior when ValueType is signed.
+  ValueType Read() const {
+    ValueType result = static_cast<ValueType>(buffer_.ReadUInt());
+    EMBOSS_CHECK(Parameters::ValueIsOk(result));
+    return result;
+  }
+  ValueType UncheckedRead() const {
+    return static_cast<ValueType>(buffer_.UncheckedReadUInt());
+  }
+  void Write(ValueType value) const { EMBOSS_CHECK(TryToWrite(value)); }
+  bool TryToWrite(ValueType value) const {
+    if (!CouldWriteValue(value)) return false;
+    if (!IsComplete()) return false;
+    buffer_.WriteUInt(static_cast<typename BitViewType::ValueType>(value));
+    return true;
+  }
+  static constexpr bool CouldWriteValue(ValueType value) {
+    // The value can be written if:
+    //
+    // a) it can fit in BitViewType::ValueType (verified by casting to
+    //    BitViewType::ValueType and back, and making sure that the value is
+    //    unchanged)
+    //
+    // and either:
+    //
+    // b1) the field size is large enough to hold all values, or
+    // b2) the value is less than 2**(field size in bits)
+    return value == static_cast<ValueType>(
+                        static_cast<typename BitViewType::ValueType>(value)) &&
+           ((Parameters::kBits ==
+             sizeof(typename BitViewType::ValueType) * 8) ||
+            (static_cast<typename BitViewType::ValueType>(value) <
+             ((static_cast<typename BitViewType::ValueType>(1)
+               << (Parameters::kBits - 1))
+              << 1))) &&
+           Parameters::ValueIsOk(value);
+  }
+  void UncheckedWrite(ValueType value) const {
+    buffer_.UncheckedWriteUInt(
+        static_cast<typename BitViewType::ValueType>(value));
+  }
+
+  template <typename OtherView>
+  void CopyFrom(const OtherView &other) const {
+    Write(other.Read());
+  }
+  template <typename OtherView>
+  void UncheckedCopyFrom(const OtherView &other) const {
+    UncheckedWrite(other.UncheckedRead());
+  }
+  template <typename OtherView>
+  bool TryToCopyFrom(const OtherView &other) const {
+    return other.Ok() && TryToWrite(other.Read());
+  }
+
+  // All bit patterns in the underlying buffer are valid, so Ok() is always
+  // true if IsComplete() is true.
+  bool Ok() const {
+    return IsComplete() && Parameters::ValueIsOk(UncheckedRead());
+  }
+  template <class OtherBitViewType>
+  bool Equals(const EnumView<Enum, Parameters, OtherBitViewType> &other) const {
+    return Read() == other.Read();
+  }
+  template <class OtherBitViewType>
+  bool UncheckedEquals(
+      const EnumView<Enum, Parameters, OtherBitViewType> &other) const {
+    return UncheckedRead() == other.UncheckedRead();
+  }
+  bool IsComplete() const {
+    return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
+  }
+
+  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);
+    }
+  }
+
+  template <class Stream>
+  void WriteToTextStream(Stream *stream,
+                         const TextOutputOptions &options) const {
+    ::emboss::support::WriteEnumViewToTextStream(this, stream, options);
+  }
+
+  static constexpr int SizeInBits() { return Parameters::kBits; }
+
+ private:
+  BitViewType buffer_;
+};
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_ENUM_VIEW_H_
diff --git a/public/emboss_enum_view_test.cc b/public/emboss_enum_view_test.cc
new file mode 100644
index 0000000..c6c90d4
--- /dev/null
+++ b/public/emboss_enum_view_test.cc
@@ -0,0 +1,275 @@
+// 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.
+
+#include "public/emboss_enum_view.h"
+
+#include "public/emboss_prelude.h"
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+namespace test {
+
+template <size_t kBits>
+using LittleEndianBitBlockN =
+    BitBlock<LittleEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>;
+
+enum class Foo : int64_t {
+  kMin = -0x7fffffffffffffffL - 1,
+  kOne = 1,
+  kTwo = 2,
+  kBig = 0x0e0f10,
+  kBigBackwards = 0x100f0e,
+  kReallyBig = 0x090a0b0c0d0e0f10L,
+  kReallyBigBackwards = 0x100f0e0d0c0b0a09L,
+  k2to24MinusOne = (1L << 24) - 1,
+  k2to24 = 1L << 24,
+  kMax = 0x7fffffffffffffffL,
+};
+
+static inline bool TryToGetEnumFromName(const char *name, Foo *result) {
+  if (!strcmp("kMin", name)) {
+    *result = Foo::kMin;
+    return true;
+  }
+  if (!strcmp("kOne", name)) {
+    *result = Foo::kOne;
+    return true;
+  }
+  if (!strcmp("kTwo", name)) {
+    *result = Foo::kTwo;
+    return true;
+  }
+  if (!strcmp("kBig", name)) {
+    *result = Foo::kBig;
+    return true;
+  }
+  if (!strcmp("kBigBackwards", name)) {
+    *result = Foo::kBigBackwards;
+    return true;
+  }
+  if (!strcmp("kReallyBig", name)) {
+    *result = Foo::kReallyBig;
+    return true;
+  }
+  if (!strcmp("kReallyBigBackwards", name)) {
+    *result = Foo::kReallyBigBackwards;
+    return true;
+  }
+  if (!strcmp("k2to24MinusOne", name)) {
+    *result = Foo::k2to24MinusOne;
+    return true;
+  }
+  if (!strcmp("k2to24", name)) {
+    *result = Foo::k2to24;
+    return true;
+  }
+  if (!strcmp("kMax", name)) {
+    *result = Foo::kMax;
+    return true;
+  }
+  return false;
+}
+
+static inline const char *TryToGetNameFromEnum(Foo value) {
+  switch (value) {
+    case Foo::kMin:
+      return "kMin";
+    case Foo::kOne:
+      return "kOne";
+    case Foo::kTwo:
+      return "kTwo";
+    case Foo::kBig:
+      return "kBig";
+    case Foo::kBigBackwards:
+      return "kBigBackwards";
+    case Foo::kReallyBig:
+      return "kReallyBig";
+    case Foo::kReallyBigBackwards:
+      return "kReallyBigBackwards";
+    case Foo::k2to24MinusOne:
+      return "k2to24MinusOne";
+    case Foo::k2to24:
+      return "k2to24";
+    case Foo::kMax:
+      return "kMax";
+    default:
+      return nullptr;
+  }
+}
+
+template <size_t kBits>
+using FooViewN = EnumView<Foo, FixedSizeViewParameters<kBits, AllValuesAreOk>,
+                          LittleEndianBitBlockN<kBits>>;
+
+template <int kMaxBits>
+void CheckEnumViewSizeInBits() {
+  const int size_in_bits =
+      EnumView<Foo, FixedSizeViewParameters<kMaxBits, AllValuesAreOk>,
+               OffsetBitBlock<LittleEndianBitBlockN<64>>>::SizeInBits();
+  EXPECT_EQ(size_in_bits, kMaxBits);
+  return CheckEnumViewSizeInBits<kMaxBits - 1>();
+}
+
+template <>
+void CheckEnumViewSizeInBits<0>() {
+  return;
+}
+
+TEST(EnumView, SizeInBits) { CheckEnumViewSizeInBits<64>(); }
+
+TEST(EnumView, ValueType) {
+  using BitBlockType = OffsetBitBlock<LittleEndianBitBlockN<64>>;
+  EXPECT_TRUE(
+      (::std::is_same<Foo,
+                      EnumView<Foo, FixedSizeViewParameters<8, AllValuesAreOk>,
+                               BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<Foo,
+                      EnumView<Foo, FixedSizeViewParameters<6, AllValuesAreOk>,
+                               BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<Foo,
+                      EnumView<Foo, FixedSizeViewParameters<33, AllValuesAreOk>,
+                               BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<Foo,
+                      EnumView<Foo, FixedSizeViewParameters<64, AllValuesAreOk>,
+                               BitBlockType>::ValueType>::value));
+}
+
+TEST(EnumView, CouldWriteValue) {
+  EXPECT_TRUE(FooViewN<64>::CouldWriteValue(Foo::kMax));
+  EXPECT_TRUE(FooViewN<64>::CouldWriteValue(Foo::kMax));
+  EXPECT_TRUE(FooViewN<24>::CouldWriteValue(Foo::k2to24MinusOne));
+  EXPECT_FALSE(FooViewN<24>::CouldWriteValue(Foo::k2to24));
+}
+
+TEST(EnumView, ReadAndWriteWithSufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}};
+  auto enum64_view = FooViewN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}};
+  EXPECT_EQ(Foo::kReallyBig, enum64_view.Read());
+  EXPECT_EQ(Foo::kReallyBig, enum64_view.UncheckedRead());
+  enum64_view.Write(Foo::kReallyBigBackwards);
+  EXPECT_EQ((::std::vector<uint8_t>{0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+                                    0x10, 0x08}),
+            bytes);
+  enum64_view.UncheckedWrite(Foo::kReallyBig);
+  EXPECT_EQ((::std::vector<uint8_t>{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a,
+                                    0x09, 0x08}),
+            bytes);
+  EXPECT_TRUE(enum64_view.TryToWrite(Foo::kReallyBigBackwards));
+  EXPECT_EQ((::std::vector<uint8_t>{0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+                                    0x10, 0x08}),
+            bytes);
+  EXPECT_TRUE(enum64_view.Ok());
+  EXPECT_TRUE(enum64_view.IsComplete());
+}
+
+TEST(EnumView, ReadAndWriteWithInsufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}};
+  auto enum64_view = FooViewN<64>{ReadWriteContiguousBuffer{bytes.data(), 4}};
+  EXPECT_DEATH(enum64_view.Read(), "");
+  EXPECT_EQ(Foo::kReallyBig, enum64_view.UncheckedRead());
+  EXPECT_DEATH(enum64_view.Write(Foo::kReallyBigBackwards), "");
+  EXPECT_FALSE(enum64_view.TryToWrite(Foo::kReallyBigBackwards));
+  EXPECT_EQ((::std::vector<uint8_t>{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a,
+                                    0x09, 0x08}),
+            bytes);
+  enum64_view.UncheckedWrite(Foo::kReallyBigBackwards);
+  EXPECT_EQ((::std::vector<uint8_t>{0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+                                    0x10, 0x08}),
+            bytes);
+  EXPECT_FALSE(enum64_view.Ok());
+  EXPECT_FALSE(enum64_view.IsComplete());
+}
+
+TEST(EnumView, NonPowerOfTwoSize) {
+  ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}};
+  auto enum24_view = FooViewN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}};
+  EXPECT_EQ(Foo::kBig, enum24_view.Read());
+  EXPECT_EQ(Foo::kBig, enum24_view.UncheckedRead());
+  enum24_view.Write(Foo::kBigBackwards);
+  EXPECT_EQ((::std::vector<uint8_t>{0x0e, 0x0f, 0x10, 0x0d}), bytes);
+  EXPECT_DEATH(enum24_view.Write(Foo::k2to24), "");
+  enum24_view.UncheckedWrite(Foo::k2to24);
+  EXPECT_EQ((::std::vector<uint8_t>{0x00, 0x00, 0x00, 0x0d}), bytes);
+  EXPECT_TRUE(enum24_view.Ok());
+  EXPECT_TRUE(enum24_view.IsComplete());
+}
+
+TEST(EnumView, NonPowerOfTwoSizeInsufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}};
+  auto enum24_view = FooViewN<24>{ReadWriteContiguousBuffer{bytes.data(), 2}};
+  EXPECT_DEATH(enum24_view.Read(), "");
+  EXPECT_EQ(Foo::kBig, enum24_view.UncheckedRead());
+  EXPECT_DEATH(enum24_view.Write(Foo::kBigBackwards), "");
+  enum24_view.UncheckedWrite(Foo::kBigBackwards);
+  EXPECT_EQ((::std::vector<uint8_t>{0x0e, 0x0f, 0x10, 0x0d}), bytes);
+  EXPECT_FALSE(enum24_view.Ok());
+  EXPECT_FALSE(enum24_view.IsComplete());
+}
+
+TEST(EnumView, UpdateFromText) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}};
+  const auto enum64_view =
+      FooViewN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}};
+  EXPECT_TRUE(UpdateFromText(enum64_view, "kBig"));
+  EXPECT_EQ(Foo::kBig, enum64_view.Read());
+  EXPECT_TRUE(UpdateFromText(enum64_view, "k2to24"));
+  EXPECT_EQ(Foo::k2to24, enum64_view.Read());
+  EXPECT_FALSE(UpdateFromText(enum64_view, "k2to24M"));
+  EXPECT_EQ(Foo::k2to24, enum64_view.Read());
+  EXPECT_TRUE(UpdateFromText(enum64_view, "k2to24MinusOne"));
+  EXPECT_EQ(Foo::k2to24MinusOne, enum64_view.Read());
+  EXPECT_TRUE(UpdateFromText(enum64_view, "0x0e0f10"));
+  EXPECT_EQ(Foo::kBig, enum64_view.Read());
+  EXPECT_TRUE(UpdateFromText(enum64_view, "0x7654321"));
+  EXPECT_EQ(static_cast<Foo>(0x7654321), enum64_view.Read());
+  EXPECT_FALSE(UpdateFromText(enum64_view, "0y0"));
+  EXPECT_EQ(static_cast<Foo>(0x7654321), enum64_view.Read());
+  EXPECT_FALSE(UpdateFromText(enum64_view, "-x"));
+  EXPECT_EQ(static_cast<Foo>(0x7654321), enum64_view.Read());
+  EXPECT_TRUE(UpdateFromText(enum64_view, "-0x8000_0000_0000_0000"));
+  EXPECT_EQ(Foo::kMin, enum64_view.Read());
+}
+
+TEST(EnumView, WriteToText) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}};
+  const auto enum64_view =
+      FooViewN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}};
+  EXPECT_EQ("kReallyBig", WriteToString(enum64_view));
+  EXPECT_EQ("kReallyBig  # 651345242494996240",
+            WriteToString(enum64_view, TextOutputOptions().WithComments(true)));
+  EXPECT_EQ("kReallyBig  # 0x90a0b0c0d0e0f10",
+            WriteToString(
+                enum64_view,
+                TextOutputOptions().WithComments(true).WithNumericBase(16)));
+  enum64_view.Write(static_cast<Foo>(123));
+  EXPECT_EQ("123", WriteToString(enum64_view));
+  EXPECT_EQ("123",
+            WriteToString(enum64_view, TextOutputOptions().WithComments(true)));
+  EXPECT_EQ("0x7b",
+            WriteToString(
+                enum64_view,
+                TextOutputOptions().WithComments(true).WithNumericBase(16)));
+}
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_maybe.h b/public/emboss_maybe.h
new file mode 100644
index 0000000..a11ba39
--- /dev/null
+++ b/public/emboss_maybe.h
@@ -0,0 +1,76 @@
+// 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.
+
+// Definition of the Maybe<T> template class.
+#ifndef EMBOSS_PUBLIC_EMBOSS_MAYBE_H_
+#define EMBOSS_PUBLIC_EMBOSS_MAYBE_H_
+
+#include <utility>
+
+#include "public/emboss_defines.h"
+
+namespace emboss {
+// TODO(bolms): Should Maybe be a public type (i.e., live in ::emboss)?
+namespace support {
+
+// Maybe<T> is similar to, but much more restricted than, C++17's std::optional.
+// It is intended for use in Emboss's expression system, wherein a non-Known()
+// Maybe<T> will usually (but not always) poison the result of an operation.
+//
+// As such, Maybe<> is intended for use with small, copyable T's: specifically,
+// integers, enums, and booleans.  It may not perform well with other types.
+template <typename T>
+class Maybe final {
+ public:
+  constexpr Maybe() : value_(), known_(false) {}
+  constexpr explicit Maybe(T value)
+      : value_(::std::move(value)), known_(true) {}
+  constexpr Maybe(const Maybe<T> &) = default;
+  ~Maybe() = default;
+  Maybe &operator=(const Maybe &) = default;
+  Maybe &operator=(T value) {
+    value_ = ::std::move(value);
+    known_ = true;
+    return *this;
+  }
+  Maybe &operator=(const T &value) {
+    value_ = value;
+    known_ = true;
+    return *this;
+  }
+
+  constexpr bool Known() const { return known_; }
+  T Value() const {
+    EMBOSS_CHECK(Known());
+    return value_;
+  }
+  constexpr T ValueOr(T default_value) const {
+    return known_ ? value_ : default_value;
+  }
+  // A non-Ok() Maybe value-initializes value_ to a default (by explicitly
+  // calling the nullary constructor on value_ in the initializer list), so it
+  // is safe to just return value_ here.  For integral types and enums, value_
+  // will be 0, for bool it will be false, and for other types it depends on the
+  // constructor's behavior.
+  constexpr T ValueOrDefault() const { return value_; }
+
+ private:
+  T value_;
+  bool known_;
+};
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_MAYBE_H_
diff --git a/public/emboss_maybe_test.cc b/public/emboss_maybe_test.cc
new file mode 100644
index 0000000..acbf917
--- /dev/null
+++ b/public/emboss_maybe_test.cc
@@ -0,0 +1,61 @@
+// 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.
+
+#include "public/emboss_maybe.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+namespace test {
+
+enum class Foo : ::std::int64_t {
+  BAR = 1,
+  BAZ = 2,
+};
+
+TEST(Maybe, Known) {
+  EXPECT_TRUE(Maybe<int>(10).Known());
+  EXPECT_EQ(10, Maybe<int>(10).ValueOr(3));
+  EXPECT_EQ(10, Maybe<int>(10).ValueOrDefault());
+  EXPECT_EQ(10, Maybe<int>(10).Value());
+  EXPECT_TRUE(Maybe<bool>(true).Value());
+  EXPECT_EQ(Foo::BAZ, Maybe<Foo>(Foo::BAZ).ValueOrDefault());
+
+  Maybe<int> x = Maybe<int>(1000);
+  Maybe<int> y = Maybe<int>();
+  y = x;
+  EXPECT_TRUE(y.Known());
+  EXPECT_EQ(1000, y.Value());
+}
+
+TEST(Maybe, Unknown) {
+  EXPECT_FALSE(Maybe<int>().Known());
+  EXPECT_EQ(3, Maybe<int>().ValueOr(3));
+  EXPECT_EQ(0, Maybe<int>().ValueOrDefault());
+  EXPECT_FALSE(Maybe<bool>().ValueOrDefault());
+  EXPECT_DEATH(Maybe<int>().Value(), "Known()");
+  EXPECT_FALSE(Maybe<bool>().ValueOrDefault());
+  EXPECT_EQ(static_cast<Foo>(0), Maybe<Foo>().ValueOrDefault());
+
+  Maybe<int> x = Maybe<int>();
+  Maybe<int> y = Maybe<int>(1000);
+  y = x;
+  EXPECT_FALSE(y.Known());
+}
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_memory_util.h b/public/emboss_memory_util.h
new file mode 100644
index 0000000..eb91138
--- /dev/null
+++ b/public/emboss_memory_util.h
@@ -0,0 +1,968 @@
+// 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.
+
+// Utilities for efficiently reading and writing to/from memory.
+#ifndef EMBOSS_PUBLIC_EMBOSS_MEMORY_UTIL_H_
+#define EMBOSS_PUBLIC_EMBOSS_MEMORY_UTIL_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <cstring>
+
+#include "public/emboss_bit_util.h"
+#include "public/emboss_cpp_types.h"
+#include "public/emboss_defines.h"
+
+namespace emboss {
+namespace support {
+
+// MemoryAccessor reads and writes big- and little-endian unsigned integers in
+// and out of memory, using optimized routines where possible.
+//
+// The default MemoryAccessor just proxies to the MemoryAccessor with the
+// next-smallest alignment and equivalent offset: MemoryAccessor<C, 8, 0, 32>
+// and MemoryAccessor<C, 8, 4, 32> will proxy to MemoryAccessor<C, 4, 0, 32>,
+// since an 8-byte-aligned pointer is also 4-byte-aligned, as is a pointer that
+// is 4 bytes away from 8-byte alignment.
+template <typename CharT, ::std::size_t kAlignment, ::std::size_t kOffset,
+          ::std::size_t kBits>
+struct MemoryAccessor {
+  static_assert(IsPowerOfTwo(kAlignment),
+                "MemoryAccessor requires power-of-two alignment.");
+  static_assert(
+      kOffset < kAlignment,
+      "MemoryAccessor requires offset to be strictly less than alignment.");
+
+  using ChainedAccessor =
+      MemoryAccessor<CharT, kAlignment / 2, kOffset % (kAlignment / 2), kBits>;
+  using Unsigned = typename LeastWidthInteger<kBits>::Unsigned;
+  static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) {
+    return ChainedAccessor::ReadLittleEndianUInt(bytes);
+  }
+  static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) {
+    ChainedAccessor::WriteLittleEndianUInt(bytes, value);
+  }
+  static inline Unsigned ReadBigEndianUInt(const CharT *bytes) {
+    return ChainedAccessor::ReadBigEndianUInt(bytes);
+  }
+  static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) {
+    ChainedAccessor::WriteBigEndianUInt(bytes, value);
+  }
+};
+
+// The least-aligned case for MemoryAccessor is 8-bit alignment, and the default
+// version of MemoryAccessor will devolve to this one if there is no more
+// specific override.
+//
+// If the system byte order is known, then these routines can use memcpy and
+// (possibly) a byte swap; otherwise they can read individual bytes and
+// shift+or them together in the appropriate order.  I (bolms@) haven't found a
+// compiler that will optimize the multiple reads, shifts, and ors into a single
+// read, so the memcpy version is *much* faster for 32-bit and larger reads.
+template <typename CharT, ::std::size_t kBits>
+struct MemoryAccessor<CharT, 1, 0, kBits> {
+  static_assert(kBits % 8 == 0,
+                "MemoryAccessor can only read and write whole-byte values.");
+  static_assert(IsChar<CharT>::value,
+                "MemoryAccessor can only be used on pointers to char types.");
+
+  using Unsigned = typename LeastWidthInteger<kBits>::Unsigned;
+
+#if defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE)
+  static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) {
+    Unsigned result = 0;
+    ::std::memcpy(&result, bytes, kBits / 8);
+    return EMBOSS_LITTLE_ENDIAN_TO_NATIVE(result);
+  }
+#else
+  static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) {
+    Unsigned result = 0;
+    for (decltype(kBits) i = 0; i < kBits / 8; ++i) {
+      result |= static_cast<Unsigned>(static_cast<uint8_t>(bytes[i])) << i * 8;
+    }
+    return result;
+  }
+#endif
+
+#if defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN)
+  static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) {
+    value = EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value);
+    ::std::memcpy(bytes, &value, kBits / 8);
+  }
+#else
+  static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) {
+    for (decltype(kBits) i = 0; i < kBits / 8; ++i) {
+      bytes[i] = static_cast<CharT>(static_cast<uint8_t>(value));
+      if (sizeof value > 1) {
+        // Shifting an 8-bit type by 8 bits is undefined behavior, so skip this
+        // step for uint8_t.
+        value >>= 8;
+      }
+    }
+  }
+#endif
+
+#if defined(EMBOSS_BIG_ENDIAN_TO_NATIVE)
+  static inline Unsigned ReadBigEndianUInt(const CharT *bytes) {
+    Unsigned result = 0;
+    // When a big-endian source integer is smaller than the result, the source
+    // bytes must be copied into the final bytes of the destination.  This is
+    // true whether the host is big- or little-endian.
+    //
+    // For a little-endian host:
+    //
+    // source (big-endian value 0x112233):
+    //
+    //   byte 0   byte 1   byte 2
+    // +--------+--------+--------+
+    // | 0x11   | 0x22   | 0x33   |
+    // +--------+--------+--------+
+    //
+    // result after memcpy (host-interpreted value 0x33221100):
+    //
+    //   byte 0   byte 1   byte 2   byte 3
+    // +--------+--------+--------+--------+
+    // | 0x00   | 0x11   | 0x22   | 0x33   |
+    // +--------+--------+--------+--------+
+    //
+    // result after 32-bit byte swap (host-interpreted value 0x112233):
+    //
+    //   byte 0   byte 1   byte 2   byte 3
+    // +--------+--------+--------+--------+
+    // | 0x33   | 0x22   | 0x11   | 0x00   |
+    // +--------+--------+--------+--------+
+    //
+    // For a big-endian host:
+    //
+    // source (value 0x112233):
+    //
+    //   byte 0   byte 1   byte 2
+    // +--------+--------+--------+
+    // | 0x11   | 0x22   | 0x33   |
+    // +--------+--------+--------+
+    //
+    // result after memcpy (value 0x112233) -- no byte swap needed:
+    //
+    //   byte 0   byte 1   byte 2   byte 3
+    // +--------+--------+--------+--------+
+    // | 0x00   | 0x11   | 0x22   | 0x33   |
+    // +--------+--------+--------+--------+
+    ::std::memcpy(reinterpret_cast<char *>(&result) + sizeof result - kBits / 8,
+                  bytes, kBits / 8);
+    result = EMBOSS_BIG_ENDIAN_TO_NATIVE(result);
+    return result;
+  }
+#else
+  static inline Unsigned ReadBigEndianUInt(const CharT *bytes) {
+    Unsigned result = 0;
+    for (decltype(kBits) i = 0; i < kBits / 8; ++i) {
+      result |= static_cast<Unsigned>(static_cast</**/::std::uint8_t>(bytes[i]))
+                << (kBits - 8 - i * 8);
+    }
+    return result;
+  }
+#endif
+
+#if defined(EMBOSS_NATIVE_TO_BIG_ENDIAN)
+  static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) {
+    value = EMBOSS_NATIVE_TO_BIG_ENDIAN(value);
+    ::std::memcpy(bytes,
+                  reinterpret_cast<char *>(&value) + sizeof value - kBits / 8,
+                  kBits / 8);
+  }
+#else
+  static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) {
+    for (decltype(kBits) i = 0; i < kBits / 8; ++i) {
+      bytes[kBits / 8 - 1 - i] =
+          static_cast<CharT>(static_cast</**/ ::std::uint8_t>(value));
+      if (sizeof value > 1) {
+        // Shifting an 8-bit type by 8 bits is undefined behavior, so skip this
+        // step for uint8_t.
+        value >>= 8;
+      }
+    }
+  }
+#endif
+};
+
+// Specialization of UIntMemoryAccessor for 16- 32- and 64-bit-aligned reads and
+// writes, using EMBOSS_ALIAS_SAFE_POINTER_CAST instead of memcpy.
+#if defined(EMBOSS_ALIAS_SAFE_POINTER_CAST) && \
+    defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) && \
+    defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) &&    \
+    defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) && \
+    defined(EMBOSS_NATIVE_TO_BIG_ENDIAN)
+template <typename CharT>
+struct MemoryAccessor<CharT, 8, 0, 64> {
+  static inline ::std::uint64_t ReadLittleEndianUInt(const CharT *bytes) {
+    return EMBOSS_LITTLE_ENDIAN_TO_NATIVE(
+        *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint64_t, bytes));
+  }
+
+  static inline void WriteLittleEndianUInt(CharT *bytes,
+                                           ::std::uint64_t value) {
+    *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint64_t, bytes) =
+        EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value);
+  }
+
+  static inline ::std::uint64_t ReadBigEndianUInt(const CharT *bytes) {
+    return EMBOSS_BIG_ENDIAN_TO_NATIVE(
+        *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint64_t, bytes));
+  }
+
+  static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint64_t value) {
+    *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint64_t, bytes) =
+        EMBOSS_NATIVE_TO_BIG_ENDIAN(value);
+  }
+};
+
+template <typename CharT>
+struct MemoryAccessor<CharT, 4, 0, 32> {
+  static inline ::std::uint32_t ReadLittleEndianUInt(const CharT *bytes) {
+    return EMBOSS_LITTLE_ENDIAN_TO_NATIVE(
+        *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint32_t, bytes));
+  }
+
+  static inline void WriteLittleEndianUInt(CharT *bytes,
+                                           ::std::uint32_t value) {
+    *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint32_t, bytes) =
+        EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value);
+  }
+
+  static inline ::std::uint32_t ReadBigEndianUInt(const CharT *bytes) {
+    return EMBOSS_BIG_ENDIAN_TO_NATIVE(
+        *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint32_t, bytes));
+  }
+
+  static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint32_t value) {
+    *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint32_t, bytes) =
+        EMBOSS_NATIVE_TO_BIG_ENDIAN(value);
+  }
+};
+
+template <typename CharT>
+struct MemoryAccessor<CharT, 2, 0, 16> {
+  static inline ::std::uint16_t ReadLittleEndianUInt(const CharT *bytes) {
+    return EMBOSS_LITTLE_ENDIAN_TO_NATIVE(
+        *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint16_t, bytes));
+  }
+
+  static inline void WriteLittleEndianUInt(CharT *bytes,
+                                           ::std::uint16_t value) {
+    *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint16_t, bytes) =
+        EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value);
+  }
+
+  static inline ::std::uint16_t ReadBigEndianUInt(const CharT *bytes) {
+    return EMBOSS_BIG_ENDIAN_TO_NATIVE(
+        *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint16_t, bytes));
+  }
+
+  static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint16_t value) {
+    *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint16_t, bytes) =
+        EMBOSS_NATIVE_TO_BIG_ENDIAN(value);
+  }
+};
+#endif  // defined(EMBOSS_ALIAS_SAFE_POINTER_CAST) &&
+        // defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) &&
+        // defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) &&
+        // defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) &&
+        // defined(EMBOSS_NATIVE_TO_BIG_ENDIAN)
+
+// This is the Euclidean GCD algorithm, in C++11-constexpr-safe form.  The
+// initial is-b-greater-than-a-if-so-swap is omitted, since gcd(b % a, a) is the
+// same as gcd(b, a) when a > b.
+inline constexpr ::std::size_t GreatestCommonDivisor(::std::size_t a,
+                                                     ::std::size_t b) {
+  return a == 0 ? b : GreatestCommonDivisor(b % a, a);
+}
+
+// ContiguousBuffer is a direct view of a fixed number of contiguous bytes in
+// memory.  If Byte is a const type, it will be a read-only view; if Byte is
+// non-const, then writes will be allowed.
+//
+// The kAlignment and kOffset parameters are used to optimize certain reads and
+// writes.  static_cast<uintptr_t>(bytes_) % kAlignment must equal kOffset.
+//
+// This class is used extensively by generated code, and is not intended to be
+// heavily used by hand-written code -- some interfaces can be tricky to call
+// correctly.
+template <typename Byte, ::std::size_t kAlignment, ::std::size_t kOffset>
+class ContiguousBuffer final {
+  // There aren't many systems with non-8-bit chars, and a quirk of POSIX
+  // requires that POSIX C systems have CHAR_BIT == 8, but some DSPs use wider
+  // chars.
+  static_assert(CHAR_BIT == 8, "ContiguousBuffer requires 8-bit chars.");
+
+  // ContiguousBuffer assumes that its backing store is byte-oriented.  The
+  // previous check ensures that chars are 8 bits, and this one ensures that the
+  // backing store uses chars.
+  //
+  // Note that this check is explicitly that Byte is one of the three standard
+  // char types, and not that (say) it is a one-byte type with an assignment
+  // operator that can be static_cast<> to and from uint8_t.  I (bolms@) have
+  // chosen to lock it down to just char types to avoid running afoul of strict
+  // aliasing rules anywhere.
+  //
+  // Of somewhat academic interest, uint8_t is not required to be a char type
+  // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66110#c10), though it is
+  // unlikely that any compiler vendor will actually change it, as there is
+  // probably enough real-world code that relies on uint8_t being allowed to
+  // alias.
+  static_assert(IsChar<Byte>::value, "ContiguousBuffer requires char type.");
+
+  // Because real-world processors only care about power-of-2 alignments,
+  // ContiguousBuffer only supports power-of-2 alignments.  Note that
+  // GetOffsetStorage can handle non-power-of-2 alignments.
+  static_assert(IsPowerOfTwo(kAlignment),
+                "ContiguousBuffer requires power-of-two alignment.");
+
+  // To avoid template variant explosion, ContiguousBuffer requires kOffset to
+  // be strictly less than kAlignment.  Users of ContiguousBuffer are expected
+  // to take the modulus of kOffset by kAlignment before passing it in as a
+  // parameter.
+  static_assert(
+      kOffset < kAlignment,
+      "ContiguousBuffer requires offset to be strictly less than alignment.");
+
+ public:
+  using ByteType = Byte;
+  // OffsetStorageType<kSubAlignment, kSubOffset> is the return type of
+  // GetOffsetStorage<kSubAlignment, kSubOffset>(...).  This is used in a number
+  // of places in generated code to specify deeply-nested template values.
+  //
+  // In theory, anything that cared about this type could use
+  // decltype(declval(ContiguousBuffer<...>).GetOffsetStorage<kSubAlignment,
+  // kSubOffset>(0, 0)) instead, but that is much more cumbersome, and it
+  // appears that at least some versions of GCC do not handle it correctly.
+  template </**/ ::std::size_t kSubAlignment, ::std::size_t kSubOffset>
+  using OffsetStorageType =
+      ContiguousBuffer<Byte, GreatestCommonDivisor(kAlignment, kSubAlignment),
+                       (kOffset + kSubOffset) %
+                           GreatestCommonDivisor(kAlignment, kSubAlignment)>;
+
+  // Constructs a default ContiguousBuffer.
+  ContiguousBuffer() : bytes_(nullptr), size_(0) {}
+
+  // Constructs a ContiguousBuffer from a contiguous container type over some
+  // `char` type, such as std::string, std::vector<signed char>,
+  // std::array<unsigned char, N>, or std::string_view.
+  //
+  // This template is only enabled if:
+  //
+  // 1. bytes->data() returns a pointer to some char type.
+  // 2. Byte is at least as cv-qualified as decltype(*bytes->data()).
+  //
+  // The first requirement means that this constructor won't work on, e.g.,
+  // std::vector<int> -- this is mostly a precautionary measure, since
+  // ContiguousBuffer only uses alias-safe operations anyway.
+  //
+  // The second requirement means that const and volatile are respected in the
+  // expected way: a ContiguousBuffer<const unsigned char, ...> may be
+  // initialized from std::vector<char>, but a ContiguousBuffer<unsigned char,
+  // ...> may not be initialized from std::string_view.
+  template <
+      typename T,
+      typename = typename ::std::enable_if<
+          IsChar<typename ::std::remove_cv<typename ::std::remove_reference<
+              decltype(*(::std::declval<T>().data()))>::type>::type>::value
+              && ::std::is_same<
+                  typename AddSourceCV<decltype(*::std::declval<T>().data()),
+                                       Byte>::Type,
+                  Byte>::value>::type>
+  explicit ContiguousBuffer(T *bytes)
+      : bytes_{reinterpret_cast<Byte *>(bytes->data())}, size_{bytes->size()} {
+    if (bytes != nullptr)
+      EMBOSS_DCHECK_POINTER_ALIGNMENT(bytes, kAlignment, kOffset);
+  }
+
+  // Constructs a ContiguousBuffer from a pointer to a char type and a size.  As
+  // with the constructor from a container, above, Byte must be at least as
+  // cv-qualified as T.
+  template <
+      typename T,
+      typename = typename ::std::enable_if<IsChar<T>::value && ::std::is_same<
+          typename AddSourceCV<T, Byte>::Type, Byte>::value>>
+  explicit ContiguousBuffer(T *bytes, ::std::size_t size)
+      : bytes_{reinterpret_cast<Byte *>(bytes)},
+        size_{bytes == nullptr ? 0 : size} {
+    if (bytes != nullptr)
+      EMBOSS_DCHECK_POINTER_ALIGNMENT(bytes, kAlignment, kOffset);
+  }
+
+  // Constructs a ContiguousBuffer from nullptr.  Equivalent to
+  // ContiguousBuffer().
+  //
+  // TODO(bolms): Update callers and remove this constructor.
+  explicit ContiguousBuffer(::std::nullptr_t) : bytes_{nullptr}, size_{0} {}
+
+  // Implicitly constructs a ContiguousBuffer from an identical
+  // ContiguousBuffer.
+  ContiguousBuffer(const ContiguousBuffer &other) = default;
+
+  // Explicitly construct a ContiguousBuffers from another, compatible
+  // ContiguousBuffer.  A compatible ContiguousBuffer has an
+  // equally-or-less-cv-qualified Byte type, an alignment that is an exact
+  // multiple of this ContiguousBuffer's alignment, and an offset that is the
+  // same when reduced to this ContiguousBuffer's alignment.
+  //
+  // The final !::std::is_same<...> clause prevents this constructor from
+  // overlapping with the *implicit* copy constructor.
+  template <
+      typename OtherByte, size_t kOtherAlignment, size_t kOtherOffset,
+      typename = typename ::std::enable_if<
+          kOtherAlignment % kAlignment == 0 &&
+          kOtherOffset % kAlignment ==
+              kOffset && ::std::is_same<
+                  typename AddSourceCV<OtherByte, Byte>::Type, Byte>::value &&
+          !::std::is_same<ContiguousBuffer,
+                          ContiguousBuffer<OtherByte, kOtherAlignment,
+                                           kOtherOffset>>::value>::type>
+  explicit ContiguousBuffer(
+      const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other)
+      : bytes_{reinterpret_cast<Byte *>(other.data())},
+        size_{other.SizeInBytes()} {}
+
+  // Assignment from a compatible ContiguousBuffer.
+  template <typename OtherByte, size_t kOtherAlignment, size_t kOtherOffset,
+            typename = typename ::std::enable_if<
+                kOtherAlignment % kAlignment == 0 &&
+                kOtherOffset % kAlignment ==
+                    kOffset && ::std::is_same<
+                        typename AddSourceCV<OtherByte, Byte>::Type,
+                        Byte>::value>::type>
+  ContiguousBuffer &operator=(
+      const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other) {
+    bytes_ = reinterpret_cast<Byte *>(other.data());
+    size_ = other.SizeInBytes();
+    return *this;
+  }
+
+  // GetOffsetStorage returns a new ContiguousBuffer that is a subsection of
+  // this ContiguousBuffer, with appropriate alignment assertions.  The new
+  // ContiguousBuffer will point to a region `offset` bytes into the original
+  // ContiguousBuffer, with a size of `max(size, original_size - offset)`.
+  //
+  // The kSubAlignment and kSubOffset template parameters act as assertions
+  // about the value of `offset`: `offset % (kSubAlignment / 8) - (kSubOffset /
+  // 8)` must be zero.  That is, if `kSubAlignment` is 16 and `kSubOffset` is 8,
+  // then `offset` may be 1, 3, 5, 7, etc.
+  //
+  // As a special case, if `kSubAlignment` is 0, then `offset` must exactly
+  // equal `kSubOffset`.
+  //
+  // This method is used by generated structure views to get backing buffers for
+  // views of their fields; the code generator can determine proper values for
+  // `kSubAlignment` and `kSubOffset`.
+  template </**/ ::std::size_t kSubAlignment, ::std::size_t kSubOffset>
+  OffsetStorageType<kSubAlignment, kSubOffset> GetOffsetStorage(
+      ::std::size_t offset, ::std::size_t size) const {
+    static_assert(kSubAlignment == 0 || kSubAlignment > kSubOffset,
+                  "kSubAlignment must be greater than kSubOffset.");
+    // Emboss provides a fast, unchecked path for reads and writes like:
+    //
+    // view.field().subfield().UncheckedWrite().
+    //
+    // Each of .field() and .subfield() call GetOffsetStorage(), so
+    // GetOffsetStorage() must be small and fast.
+    if (kSubAlignment == 0) {
+      EMBOSS_DCHECK_EQ(offset, kSubOffset);
+    } else {
+      // The weird ?:, below, silences -Werror=div-by-zero on versions of GCC
+      // that aren't smart enough to figure out that kSubAlignment can't be zero
+      // in this branch.
+      EMBOSS_DCHECK_EQ(offset % (kSubAlignment == 0 ? 1 : kSubAlignment),
+                       kSubOffset);
+    }
+    using ResultStorageType = OffsetStorageType<kSubAlignment, kSubOffset>;
+    return bytes_ == nullptr
+               ? ResultStorageType{nullptr}
+               : ResultStorageType{
+                     bytes_ + offset,
+                     size_ < offset ? 0 : ::std::min(size, size_ - offset)};
+  }
+
+  // ReadLittleEndianUInt, ReadBigEndianUInt, and the unchecked versions thereof
+  // provide efficient multibyte read access to the underlying buffer.  The
+  // kBits template parameter should always equal the buffer size when these are
+  // called.
+  //
+  // Generally, types other than unsigned integers can be relatively efficiently
+  // converted from unsigned integers, and views should use Read...UInt to read
+  // the raw value, then convert.
+  //
+  // Read...UInt always reads the entire buffer; to read a smaller section, use
+  // GetOffsetStorage first.
+  template </**/ ::std::size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned ReadLittleEndianUInt() const {
+    EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits);
+    EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset);
+    return UncheckedReadLittleEndianUInt<kBits>();
+  }
+  template </**/ ::std::size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned UncheckedReadLittleEndianUInt()
+      const {
+    static_assert(kBits % 8 == 0,
+                  "ContiguousBuffer::ReadLittleEndianUInt() can only read "
+                  "whole-byte values.");
+    return MemoryAccessor<Byte, kAlignment, kOffset,
+                          kBits>::ReadLittleEndianUInt(bytes_);
+  }
+  template </**/ ::std::size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned ReadBigEndianUInt() const {
+    EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits);
+    EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset);
+    return UncheckedReadBigEndianUInt<kBits>();
+  }
+  template </**/ ::std::size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned UncheckedReadBigEndianUInt()
+      const {
+    static_assert(kBits % 8 == 0,
+                  "ContiguousBuffer::ReadBigEndianUInt() can only read "
+                  "whole-byte values.");
+    return MemoryAccessor<Byte, kAlignment, kOffset, kBits>::ReadBigEndianUInt(
+        bytes_);
+  }
+
+  // WriteLittleEndianUInt, WriteBigEndianUInt, and the unchecked versions
+  // thereof provide efficient write access to the buffer.  Similar to the Read
+  // methods above, they write the entire buffer from an unsigned integer;
+  // non-unsigned values should be converted to the equivalent bit pattern, then
+  // written, and to write a subsection of the buffer use GetOffsetStorage
+  // first.
+  template </**/ ::std::size_t kBits>
+  void WriteLittleEndianUInt(
+      typename LeastWidthInteger<kBits>::Unsigned value) const {
+    EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits);
+    EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset);
+    UncheckedWriteLittleEndianUInt<kBits>(value);
+  }
+  template </**/ ::std::size_t kBits>
+  void UncheckedWriteLittleEndianUInt(
+      typename LeastWidthInteger<kBits>::Unsigned value) const {
+    static_assert(kBits % 8 == 0,
+                  "ContiguousBuffer::WriteLittleEndianUInt() can only write "
+                  "whole-byte values.");
+    MemoryAccessor<Byte, kAlignment, kOffset, kBits>::WriteLittleEndianUInt(
+        bytes_, value);
+  }
+  template </**/ ::std::size_t kBits>
+  void WriteBigEndianUInt(
+      typename LeastWidthInteger<kBits>::Unsigned value) const {
+    EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits);
+    EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset);
+    return UncheckedWriteBigEndianUInt<kBits>(value);
+  }
+  template </**/ ::std::size_t kBits>
+  void UncheckedWriteBigEndianUInt(
+      typename LeastWidthInteger<kBits>::Unsigned value) const {
+    static_assert(kBits % 8 == 0,
+                  "ContiguousBuffer::WriteBigEndianUInt() can only write "
+                  "whole-byte values.");
+    MemoryAccessor<Byte, kAlignment, kOffset, kBits>::WriteBigEndianUInt(bytes_,
+                                                                         value);
+  }
+
+  template <typename OtherByte, ::std::size_t kOtherAlignment,
+            ::std::size_t kOtherOffset>
+  void UncheckedCopyFrom(
+      const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other,
+      ::std::size_t size) const {
+    memmove(data(), other.data(), size);
+  }
+  template <typename OtherByte, ::std::size_t kOtherAlignment,
+            ::std::size_t kOtherOffset>
+  void CopyFrom(
+      const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other,
+      ::std::size_t size) const {
+    EMBOSS_CHECK(Ok());
+    EMBOSS_CHECK(other.Ok());
+    // It is OK if either buffer contains extra bytes that are not being copied.
+    EMBOSS_CHECK_GE(SizeInBytes(), size);
+    EMBOSS_CHECK_GE(other.SizeInBytes(), size);
+    UncheckedCopyFrom(other, size);
+  }
+  template <typename OtherByte, ::std::size_t kOtherAlignment,
+            ::std::size_t kOtherOffset>
+  bool TryToCopyFrom(
+      const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other,
+      ::std::size_t size) const {
+    if (Ok() && other.Ok() && SizeInBytes() >= size &&
+        other.SizeInBytes() >= size) {
+      UncheckedCopyFrom(other, size);
+      return true;
+    }
+    return false;
+  }
+  ::std::size_t SizeInBytes() const { return size_; }
+  bool Ok() const { return bytes_ != nullptr; }
+  Byte *data() const { return bytes_; }
+  Byte *begin() const { return bytes_; }
+  Byte *end() const { return bytes_ + size_; }
+
+ private:
+  Byte *bytes_ = nullptr;
+  ::std::size_t size_ = 0;
+};
+
+// TODO(bolms): Remove these aliases.
+using ReadWriteContiguousBuffer = ContiguousBuffer<unsigned char, 1, 0>;
+using ReadOnlyContiguousBuffer = ContiguousBuffer<const unsigned char, 1, 0>;
+
+// LittleEndianByteOrderer is a pass-through adapter for a byte buffer class.
+// It is used to implement little-endian bit blocks.
+//
+// When used by BitBlock, the resulting bits are numbered as if they are
+// little-endian:
+//
+//                      bit addresses of each bit in each byte
+//           +----+----+----+----+----+----+----+----+----+----+----+----+----
+// bit in  7 |  7 | 15 | 23 | 31 | 39 | 47 | 55 | 63 | 71 | 79 | 87 | 95 |
+// byte    6 |  6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 | 70 | 78 | 86 | 94 |
+//         5 |  5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 | 69 | 77 | 85 | 93 |
+//         4 |  4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 | 68 | 76 | 84 | 92 |
+//         3 |  3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 | 67 | 75 | 83 | 91 | ...
+//         2 |  2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 | 66 | 74 | 82 | 90 |
+//         1 |  1 |  9 | 17 | 25 | 33 | 41 | 49 | 57 | 65 | 73 | 81 | 89 |
+//         0 |  0 |  8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 |
+//           +----+----+----+----+----+----+----+----+----+----+----+----+----
+//              0    1    2    3    4    5    6    7    8    9   10   11   ...
+//                                  byte address
+//
+// Because endian-specific reads and writes are handled in ContiguousBuffer,
+// this class exists mostly to translate VerbUInt calls to VerbLittleEndianUInt.
+template <class BufferT>
+class LittleEndianByteOrderer final {
+ public:
+  // Type declaration so that BitBlock can use BufferType::BufferType.
+  using BufferType = BufferT;
+
+  LittleEndianByteOrderer() : buffer_() {}
+  explicit LittleEndianByteOrderer(BufferType buffer) : buffer_{buffer} {}
+  LittleEndianByteOrderer(const LittleEndianByteOrderer &other) = default;
+  LittleEndianByteOrderer(LittleEndianByteOrderer &&other) = default;
+  LittleEndianByteOrderer &operator=(const LittleEndianByteOrderer &other) =
+      default;
+
+  // LittleEndianByteOrderer just passes straight through to the underlying
+  // buffer.
+  bool Ok() const { return buffer_.Ok(); }
+  size_t SizeInBytes() const { return buffer_.SizeInBytes(); }
+
+  template <size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const {
+    return buffer_.template ReadLittleEndianUInt<kBits>();
+  }
+  template <size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const {
+    return buffer_.template UncheckedReadLittleEndianUInt<kBits>();
+  }
+  template <size_t kBits>
+  void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const {
+    buffer_.template WriteLittleEndianUInt<kBits>(value);
+  }
+  template <size_t kBits>
+  void UncheckedWriteUInt(
+      typename LeastWidthInteger<kBits>::Unsigned value) const {
+    buffer_.template UncheckedWriteLittleEndianUInt<kBits>(value);
+  }
+
+ private:
+  BufferType buffer_;
+};
+
+// BigEndianByteOrderer is an adapter for a byte buffer class which reverses
+// the addresses of the underlying byte buffer.  It is used to implement
+// big-endian bit blocks.
+//
+// When used by BitBlock, the resulting bits are numbered with "bit 0" as the
+// lowest-order bit of the *last* byte in the buffer.  For example, for a
+// 12-byte buffer, the bit ordering looks like:
+//
+//                      bit addresses of each bit in each byte
+//           +----+----+----+----+----+----+----+----+----+----+----+----+
+// bit in  7 | 95 | 87 | 79 | 71 | 63 | 55 | 47 | 39 | 31 | 23 | 15 |  7 |
+// byte    6 | 94 | 86 | 78 | 70 | 62 | 54 | 46 | 38 | 30 | 22 | 14 |  6 |
+//         5 | 93 | 85 | 77 | 69 | 61 | 53 | 45 | 37 | 29 | 21 | 13 |  5 |
+//         4 | 92 | 84 | 76 | 68 | 60 | 52 | 44 | 36 | 28 | 20 | 12 |  4 |
+//         3 | 91 | 83 | 75 | 67 | 59 | 51 | 43 | 35 | 27 | 19 | 11 |  3 |
+//         2 | 90 | 82 | 74 | 66 | 58 | 50 | 42 | 34 | 26 | 18 | 10 |  2 |
+//         1 | 89 | 81 | 73 | 65 | 57 | 49 | 41 | 33 | 25 | 17 |  9 |  1 |
+//         0 | 88 | 80 | 72 | 64 | 56 | 48 | 40 | 32 | 24 | 16 |  8 |  0 |
+//           +----+----+----+----+----+----+----+----+----+----+----+----+
+//              0    1    2    3    4    5    6    7    8    9   10   11
+//                                  byte address
+//
+// Note that some big-endian protocols are documented with "bit 0" being the
+// *high-order* bit of a number, in which case "bit 0" would be the
+// highest-order bit of the first byte in the buffer.  The "bit 0 is the
+// high-order bit" style seems to be more common in older documents (e.g., RFCs
+// 791 and 793, for IP and TCP), while the Emboss-style "bit 0 is in the last
+// byte" seems to be more common in newer documents (e.g., the hardware user
+// manuals bolms@ examined).
+// TODO(bolms): Examine more documents to see if the old vs new pattern holds.
+//
+// Because endian-specific reads and writes are handled in ContiguousBuffer,
+// this class exists mostly to translate VerbUInt calls to VerbBigEndianUInt.
+template <class BufferT>
+class BigEndianByteOrderer final {
+ public:
+  // Type declaration so that BitBlock can use BufferType::BufferType.
+  using BufferType = BufferT;
+
+  BigEndianByteOrderer() : buffer_() {}
+  explicit BigEndianByteOrderer(BufferType buffer) : buffer_{buffer} {}
+  BigEndianByteOrderer(const BigEndianByteOrderer &other) = default;
+  BigEndianByteOrderer(BigEndianByteOrderer &&other) = default;
+  BigEndianByteOrderer &operator=(const BigEndianByteOrderer &other) = default;
+
+  // Ok() and SizeInBytes() get passed through with no changes.
+  bool Ok() const { return buffer_.Ok(); }
+  size_t SizeInBytes() const { return buffer_.SizeInBytes(); }
+
+  template <size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const {
+    return buffer_.template ReadBigEndianUInt<kBits>();
+  }
+  template <size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const {
+    return buffer_.template UncheckedReadBigEndianUInt<kBits>();
+  }
+  template <size_t kBits>
+  void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const {
+    buffer_.template WriteBigEndianUInt<kBits>(value);
+  }
+  template <size_t kBits>
+  void UncheckedWriteUInt(
+      typename LeastWidthInteger<kBits>::Unsigned value) const {
+    buffer_.template UncheckedWriteBigEndianUInt<kBits>(value);
+  }
+
+ private:
+  BufferType buffer_;
+};
+
+// NullByteOrderer is a pass-through adapter for a byte buffer class.  It is
+// used to implement single-byte bit blocks, where byte order does not matter.
+//
+// Technically, it should be valid to swap in BigEndianByteOrderer or
+// LittleEndianByteOrderer anywhere that NullByteOrderer is used, but
+// NullByteOrderer contains a few extra CHECKs to ensure it is being used
+// correctly.
+template <class BufferT>
+class NullByteOrderer final {
+ public:
+  // Type declaration so that BitBlock can use BufferType::BufferType.
+  using BufferType = BufferT;
+
+  NullByteOrderer() : buffer_() {}
+  explicit NullByteOrderer(BufferType buffer) : buffer_{buffer} {}
+  NullByteOrderer(const NullByteOrderer &other) = default;
+  NullByteOrderer(NullByteOrderer &&other) = default;
+  NullByteOrderer &operator=(const NullByteOrderer &other) = default;
+
+  bool Ok() const { return buffer_.Ok(); }
+  size_t SizeInBytes() const { return Ok() ? 1 : 0; }
+
+  template <size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const {
+    static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values.");
+    return buffer_.template ReadLittleEndianUInt<kBits>();
+  }
+  template <size_t kBits>
+  typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const {
+    static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values.");
+    return buffer_.template UncheckedReadLittleEndianUInt<kBits>();
+  }
+  template <size_t kBits>
+  void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const {
+    static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values.");
+    buffer_.template WriteBigEndianUInt<kBits>(value);
+  }
+  template <size_t kBits>
+  void UncheckedWriteUInt(
+      typename LeastWidthInteger<kBits>::Unsigned value) const {
+    static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values.");
+    buffer_.template UncheckedWriteBigEndianUInt<kBits>(value);
+  }
+
+ private:
+  BufferType buffer_;
+};
+
+// OffsetBitBlock is a filter on another BitBlock class, which adds a fixed
+// offset to reads from underlying bit block.  This is used by Emboss generated
+// classes to read bitfields: the parent provides an OffsetBitBlock of its
+// buffer to the child's view.
+//
+// OffsetBitBlock is always statically sized, but because
+// BitBlock::GetOffsetStorage and OffsetBitBlock::GetOffsetStorage must have the
+// same signature as ContiguousBuffer::GetOffsetStorage, OffsetBitBlock's size
+// parameter must be a runtime value.
+//
+// TODO(bolms): Figure out how to add size as a template parameter to
+// OffsetBitBlock.
+template <class UnderlyingBitBlockType>
+class OffsetBitBlock final {
+ public:
+  using ValueType = typename UnderlyingBitBlockType::ValueType;
+  // Bit blocks do not use alignment information, but generated code expects bit
+  // blocks to have the same methods and types as byte blocks, so even though
+  // kNewAlignment and kNewOffset are unused, they must be present as template
+  // parameters.
+  template <size_t kNewAlignment, size_t kNewOffset>
+  using OffsetStorageType = OffsetBitBlock<UnderlyingBitBlockType>;
+
+  OffsetBitBlock() : bit_block_(), offset_(0), size_(0), ok_(false) {}
+  explicit OffsetBitBlock(UnderlyingBitBlockType bit_block, size_t offset,
+                          size_t size, bool ok)
+      : bit_block_{bit_block},
+        offset_{static_cast<uint8_t>(offset)},
+        size_{static_cast<uint8_t>(size)},
+        ok_{offset == offset_ && size == size_ && ok} {}
+  OffsetBitBlock(const OffsetBitBlock &other) = default;
+  OffsetBitBlock &operator=(const OffsetBitBlock &other) = default;
+
+  template <size_t kNewAlignment, size_t kNewOffset>
+  OffsetStorageType<kNewAlignment, kNewOffset> GetOffsetStorage(
+      size_t offset, size_t size) const {
+    return OffsetStorageType<kNewAlignment, kNewOffset>{
+        bit_block_, offset_ + offset, size, ok_ && offset + size <= size_};
+  }
+
+  // ReadUInt reads the entire underlying bit block, then shifts and masks to
+  // the appropriate size.
+  ValueType ReadUInt() const {
+    EMBOSS_CHECK_GE(bit_block_.SizeInBits(), offset_ + size_);
+    EMBOSS_CHECK(Ok());
+    return MaskToNBits(bit_block_.ReadUInt(), offset_ + size_) >> offset_;
+  }
+  ValueType UncheckedReadUInt() const {
+    return MaskToNBits(bit_block_.UncheckedReadUInt(), offset_ + size_) >>
+           offset_;
+  }
+
+  // WriteUInt writes the entire underlying bit block; in order to only write
+  // the specific bits that should be changed, the current value is first read,
+  // then masked out and or'ed with the new value, and finally the result is
+  // written back to memory.
+  void WriteUInt(ValueType value) const {
+    EMBOSS_CHECK_EQ(value, MaskToNBits(value, size_));
+    EMBOSS_CHECK(Ok());
+    // OffsetBitBlock::WriteUInt *always* does a read-modify-write because it is
+    // assumed that if the user wanted to read or write the entire value they
+    // would just use the underlying BitBlock directly.  This is mostly true for
+    // code generated by Emboss, which only uses OffsetBitBlock for subfields of
+    // `bits` types; bit-oriented types such as `UInt` will use BitBlock
+    // directly when they are placed directly in a `struct`.
+    bit_block_.WriteUInt(MaskInValue(bit_block_.ReadUInt(), value));
+  }
+  void UncheckedWriteUInt(ValueType value) const {
+    bit_block_.UncheckedWriteUInt(
+        MaskInValue(bit_block_.UncheckedReadUInt(), value));
+  }
+
+  size_t SizeInBits() const { return size_; }
+  bool Ok() const { return ok_; }
+
+ private:
+  ValueType MaskInValue(ValueType original_value, ValueType new_value) const {
+    ValueType original_mask =
+        ~(MaskToNBits(static_cast<ValueType>(~ValueType{0}), size_) << offset_);
+    return (original_value & original_mask) | (new_value << offset_);
+  }
+
+  const UnderlyingBitBlockType bit_block_;
+  const uint8_t offset_;
+  const uint8_t size_;
+  const uint8_t ok_;
+};
+
+// BitBlock is a view of a short, fixed-size sequence of bits somewhere in
+// memory.  Big- and little-endian values are handled by BufferType, which is
+// typically BigEndianByteOrderer<ContiguousBuffer<...>> or
+// LittleEndianByteOrderer<ContiguousBuffer<...>>.
+//
+// BitBlock is implemented such that it always reads and writes its entire
+// buffer; unlike ContiguousBuffer for bytes, there is no way to modify part of
+// the underlying data without doing a read-modify-write of the full value.
+// This sidesteps a lot of weirdness with converting between bit addresses and
+// byte addresses for big-endian values, though it does mean that in certain
+// cases excess bits will be read or written, particularly if care is not taken
+// in the .emb definition to keep `bits` types to a minimum size.
+template <class BufferType, size_t kBufferSizeInBits>
+class BitBlock final {
+  static_assert(kBufferSizeInBits % 8 == 0,
+                "BitBlock can only operate on byte buffers.");
+  static_assert(kBufferSizeInBits <= 64,
+                "BitBlock can only operate on small buffers.");
+
+ public:
+  using ValueType = typename LeastWidthInteger<kBufferSizeInBits>::Unsigned;
+  // As with OffsetBitBlock::OffsetStorageType, the kNewAlignment and kNewOffset
+  // values are not used, but they must be template parameters so that generated
+  // code can work with both BitBlock and ContiguousBuffer.
+  template <size_t kNewAlignment, size_t kNewOffset>
+  using OffsetStorageType =
+      OffsetBitBlock<BitBlock<BufferType, kBufferSizeInBits>>;
+
+  explicit BitBlock() : buffer_() {}
+  explicit BitBlock(BufferType buffer) : buffer_{buffer} {}
+  explicit BitBlock(typename BufferType::BufferType buffer) : buffer_{buffer} {}
+  BitBlock(const BitBlock &) = default;
+  BitBlock(BitBlock &&) = default;
+  BitBlock &operator=(const BitBlock &) = default;
+  BitBlock &operator=(BitBlock &&) = default;
+  ~BitBlock() = default;
+
+  static constexpr size_t Bits() { return kBufferSizeInBits; }
+
+  template <size_t kNewAlignment, size_t kNewOffset>
+  OffsetStorageType<kNewAlignment, kNewOffset> GetOffsetStorage(
+      size_t offset, size_t size) const {
+    return OffsetStorageType<kNewAlignment, kNewOffset>{
+        *this, offset, size, Ok() && offset + size <= kBufferSizeInBits};
+  }
+
+  // BitBlock clients must read or write the entire BitBlock value as an
+  // unsigned integer.  OffsetBitBlock can be used to extract a portion of the
+  // value via shift and mask, and individual view types such as IntView or
+  // BcdView are expected to convert ValueType to/from their desired types.
+  ValueType ReadUInt() const {
+    return buffer_.template ReadUInt<kBufferSizeInBits>();
+  }
+  ValueType UncheckedReadUInt() const {
+    return buffer_.template UncheckedReadUInt<kBufferSizeInBits>();
+  }
+  void WriteUInt(ValueType value) const {
+    EMBOSS_CHECK_EQ(value, MaskToNBits(value, kBufferSizeInBits));
+    buffer_.template WriteUInt<kBufferSizeInBits>(value);
+  }
+  void UncheckedWriteUInt(ValueType value) const {
+    buffer_.template UncheckedWriteUInt<kBufferSizeInBits>(value);
+  }
+
+  size_t SizeInBits() const { return kBufferSizeInBits; }
+  bool Ok() const {
+    return buffer_.Ok() && buffer_.SizeInBytes() * 8 == kBufferSizeInBits;
+  }
+
+ private:
+  BufferType buffer_;
+};
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_MEMORY_UTIL_H_
diff --git a/public/emboss_memory_util_test.cc b/public/emboss_memory_util_test.cc
new file mode 100644
index 0000000..9ac20db
--- /dev/null
+++ b/public/emboss_memory_util_test.cc
@@ -0,0 +1,641 @@
+// 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.
+
+#include "public/emboss_memory_util.h"
+
+#include "public/emboss_prelude.h"
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+namespace test {
+
+using ::emboss::prelude::IntView;
+using ::emboss::prelude::UIntView;
+
+template <size_t kBits>
+using BigEndianBitBlockN =
+    BitBlock<BigEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>;
+
+template <size_t kBits>
+using LittleEndianBitBlockN =
+    BitBlock<LittleEndianByteOrderer<ReadWriteContiguousBuffer>, kBits>;
+
+TEST(GreatestCommonDivisor, GreatestCommonDivisor) {
+  EXPECT_EQ(4, GreatestCommonDivisor(12, 20));
+  EXPECT_EQ(4, GreatestCommonDivisor(20, 12));
+  EXPECT_EQ(4, GreatestCommonDivisor(20, 4));
+  EXPECT_EQ(6, GreatestCommonDivisor(12, 78));
+  EXPECT_EQ(6, GreatestCommonDivisor(6, 0));
+  EXPECT_EQ(6, GreatestCommonDivisor(0, 6));
+  EXPECT_EQ(3, GreatestCommonDivisor(9, 6));
+  EXPECT_EQ(0, GreatestCommonDivisor(0, 0));
+}
+
+// Because MemoryAccessor's parameters are template parameters, it is not
+// possible to loop through them directly.  Instead, TestMemoryAccessor tests
+// a particular MemoryAccessor's methods, then calls the next template to test
+// the next set of template parameters to MemoryAccessor.
+template <typename CharT, size_t kAlignment, size_t kOffset, size_t kBits>
+void TestMemoryAccessor() {
+  alignas(kAlignment)
+      CharT bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
+  EXPECT_EQ(
+      0x0807060504030201UL & (~0x0UL >> (64 - kBits)),
+      (MemoryAccessor<CharT, kAlignment, kOffset, kBits>::ReadLittleEndianUInt(
+          bytes)))
+      << "kAlignment = " << kAlignment << "; kOffset = " << kOffset
+      << "; kBits = " << kBits;
+  EXPECT_EQ(
+      0x0102030405060708UL >> (64 - kBits),
+      (MemoryAccessor<CharT, kAlignment, kOffset, kBits>::ReadBigEndianUInt(
+          bytes)))
+      << "kAlignment = " << kAlignment << "; kOffset = " << kOffset
+      << "; kBits = " << kBits;
+
+  MemoryAccessor<CharT, kAlignment, kOffset, kBits>::WriteLittleEndianUInt(
+      bytes, 0x7172737475767778UL & (~0x0UL >> (64 - kBits)));
+  ::std::vector<CharT> expected_vector_after_write = {
+      {0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71}};
+  for (int i = kBits / 8; i < 8; ++i) {
+    expected_vector_after_write[i] = i + 1;
+  }
+  EXPECT_EQ(expected_vector_after_write,
+            ::std::vector<CharT>(bytes, bytes + sizeof bytes))
+      << "kAlignment = " << kAlignment << "; kOffset = " << kOffset
+      << "; kBits = " << kBits;
+
+  MemoryAccessor<CharT, kAlignment, kOffset, kBits>::WriteBigEndianUInt(
+      bytes, 0x7172737475767778UL >> (64 - kBits));
+  expected_vector_after_write = {
+      {0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78}};
+  for (int i = kBits / 8; i < 8; ++i) {
+    expected_vector_after_write[i] = i + 1;
+  }
+  EXPECT_EQ(expected_vector_after_write,
+            ::std::vector<CharT>(bytes, bytes + sizeof bytes))
+      << "kAlignment = " << kAlignment << "; kOffset = " << kOffset
+      << "; kBits = " << kBits;
+
+  // Recursively iterate the template:
+  //
+  // For every kAlignment/kOffset pair, check kBits from 64 to 8 in increments
+  // of 8.
+  //
+  // If kBits is 8, reset kBits to 64 and go to the next kAlignment/kOffset
+  // pair.
+  //
+  // For each kAlignment, try all kOffsets from 0 to kAlignment - 1.
+  //
+  // If kBits is 8 and kOffset is kAlignment - 1, reset kBits to 64, kOffset to
+  // 0, and halve kAlignment.
+  //
+  // Base cases below handle kAlignment == 0, terminating the recursion.
+  TestMemoryAccessor<
+      CharT,
+      kBits == 8 && kAlignment == kOffset + 1 ? kAlignment / 2 : kAlignment,
+      kBits == 8 ? kAlignment == kOffset + 1 ? 0 : kOffset + 1 : kOffset,
+      kBits == 8 ? 64 : kBits - 8>();
+}
+
+template <>
+void TestMemoryAccessor<char, 0, 0, 64>() {}
+
+template <>
+void TestMemoryAccessor<signed char, 0, 0, 64>() {}
+
+template <>
+void TestMemoryAccessor<unsigned char, 0, 0, 64>() {}
+
+TEST(MemoryAccessor, LittleEndianReads) {
+  TestMemoryAccessor<char, 8, 0, 64>();
+  TestMemoryAccessor<signed char, 8, 0, 64>();
+  TestMemoryAccessor<unsigned char, 8, 0, 64>();
+}
+
+TEST(ContiguousBuffer, OffsetStorageType) {
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 2, 0>,
+               ContiguousBuffer<char, 2, 0>::OffsetStorageType<2, 0>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 2, 0>,
+               ContiguousBuffer<char, 2, 0>::OffsetStorageType<0, 0>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 2, 0>,
+               ContiguousBuffer<char, 2, 0>::OffsetStorageType<4, 0>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 2, 0>,
+               ContiguousBuffer<char, 4, 0>::OffsetStorageType<2, 0>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 2, 0>,
+               ContiguousBuffer<char, 4, 2>::OffsetStorageType<2, 0>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 2, 0>,
+               ContiguousBuffer<char, 4, 1>::OffsetStorageType<2, 1>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 4, 2>,
+               ContiguousBuffer<char, 4, 1>::OffsetStorageType<4, 1>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 4, 1>,
+               ContiguousBuffer<char, 4, 3>::OffsetStorageType<0, 2>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 4, 1>,
+               ContiguousBuffer<char, 4, 3>::OffsetStorageType<4, 2>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 4, 1>,
+               ContiguousBuffer<char, 4, 3>::OffsetStorageType<8, 6>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 4, 1>,
+               ContiguousBuffer<char, 4, 3>::OffsetStorageType<12, 6>>::value));
+  EXPECT_TRUE((::std::is_same<
+               ContiguousBuffer<char, 1, 0>,
+               ContiguousBuffer<char, 4, 1>::OffsetStorageType<3, 1>>::value));
+}
+
+// Minimal class that forwards to std::allocator.  Used to test that
+// ReadOnlyContiguousBuffer can be constructed from std::vector<> and
+// std::basic_string<> with non-default trailing template parameters.
+template <class T>
+struct NonstandardAllocator {
+  using value_type = typename ::std::allocator<T>::value_type;
+  using pointer = typename ::std::allocator<T>::pointer;
+  using const_pointer = typename ::std::allocator<T>::const_pointer;
+  using reference = typename ::std::allocator<T>::reference;
+  using const_reference = typename ::std::allocator<T>::const_reference;
+  using size_type = typename ::std::allocator<T>::size_type;
+  using difference_type = typename ::std::allocator<T>::difference_type;
+
+  template <class U>
+  struct rebind {
+    using other = NonstandardAllocator<U>;
+  };
+
+  NonstandardAllocator() = default;
+  // This constructor is *not* explicit in order to conform to the requirements
+  // for an allocator.
+  template <class U>
+  NonstandardAllocator(const NonstandardAllocator<U> &) {}  // NOLINT
+
+  T *allocate(size_t n) { return ::std::allocator<T>().allocate(n); }
+  void deallocate(T *p, size_t n) { ::std::allocator<T>().deallocate(p, n); }
+
+  static size_type max_size() {
+    return ::std::numeric_limits<size_type>::max() / sizeof(value_type);
+  }
+};
+
+template <class T, class U>
+bool operator==(const NonstandardAllocator<T> &,
+                const NonstandardAllocator<U> &) {
+  return true;
+}
+
+template <class T, class U>
+bool operator!=(const NonstandardAllocator<T> &,
+                const NonstandardAllocator<U> &) {
+  return false;
+}
+
+// ContiguousBuffer tests for std::vector, std::array, and std::string types.
+template <typename T>
+class ReadOnlyContiguousBufferTest : public ::testing::Test {};
+typedef ::testing::Types<
+    /**/ ::std::vector<char>, ::std::array<char, 8>,
+    ::std::vector<unsigned char>, ::std::vector<signed char>, ::std::string,
+    ::std::basic_string<signed char>, ::std::basic_string<unsigned char>,
+    ::std::vector<unsigned char, NonstandardAllocator<unsigned char>>,
+    ::std::basic_string<char, ::std::char_traits<char>,
+                        NonstandardAllocator<char>>>
+    ReadOnlyContiguousContainerTypes;
+TYPED_TEST_SUITE(ReadOnlyContiguousBufferTest,
+                 ReadOnlyContiguousContainerTypes);
+
+TYPED_TEST(ReadOnlyContiguousBufferTest, ConstructionFromContainers) {
+  const TypeParam bytes = {{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}};
+  using CharType =
+      typename ::std::remove_reference<decltype(*bytes.data())>::type;
+  const auto buffer = ContiguousBuffer<const CharType, 1, 0>{&bytes};
+  EXPECT_EQ(bytes.size(), buffer.SizeInBytes());
+  EXPECT_TRUE(buffer.Ok());
+  EXPECT_EQ(0x0807060504030201UL, buffer.template ReadBigEndianUInt<64>());
+
+  const auto offset_buffer = buffer.template GetOffsetStorage<1, 0>(4, 4);
+  EXPECT_EQ(4, offset_buffer.SizeInBytes());
+  EXPECT_EQ(0x04030201U, offset_buffer.template ReadBigEndianUInt<32>());
+
+  // The size of the resulting buffer should be the minimum of the available
+  // size and the requested size.
+  EXPECT_EQ(bytes.size() - 4,
+            (buffer.template GetOffsetStorage<1, 0>(2, bytes.size() - 4)
+                 .SizeInBytes()));
+  EXPECT_EQ(
+      0,
+      (buffer.template GetOffsetStorage<1, 0>(bytes.size(), 4).SizeInBytes()));
+}
+
+// ContiguousBuffer tests for std::vector and std::array types.
+template <typename T>
+class ReadWriteContiguousBufferTest : public ::testing::Test {};
+typedef ::testing::Types</**/ ::std::vector<char>, ::std::array<char, 8>,
+                         ::std::vector<unsigned char>,
+                         ::std::vector<signed char>>
+    ReadWriteContiguousContainerTypes;
+TYPED_TEST_SUITE(ReadWriteContiguousBufferTest,
+                 ReadWriteContiguousContainerTypes);
+
+TYPED_TEST(ReadWriteContiguousBufferTest, ConstructionFromContainers) {
+  TypeParam bytes = {{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}};
+  using CharType =
+      typename ::std::remove_reference<decltype(*bytes.data())>::type;
+  const auto buffer = ContiguousBuffer<CharType, 1, 0>{&bytes};
+
+  // Read and Ok methods should work just as in ReadOnlyContiguousBuffer.
+  EXPECT_EQ(bytes.size(), buffer.SizeInBytes());
+  EXPECT_TRUE(buffer.Ok());
+  EXPECT_EQ(0x0807060504030201UL, buffer.template ReadBigEndianUInt<64>());
+
+  buffer.template WriteBigEndianUInt<64>(0x0102030405060708UL);
+  EXPECT_EQ((TypeParam{{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}),
+            bytes);
+
+  bytes[4] = static_cast<CharType>(255);
+  EXPECT_EQ(0x1020304ff060708, buffer.template ReadBigEndianUInt<64>());
+}
+
+TEST(ContiguousBuffer, ReturnTypeOfReadUInt) {
+  const auto buffer = ContiguousBuffer<char, 1, 0>();
+
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<64>()),
+                              uint64_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<48>()),
+                              uint64_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<32>()),
+                              uint32_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadBigEndianUInt<16>()),
+                              uint16_t>::value));
+  EXPECT_TRUE((
+      ::std::is_same<decltype(buffer.ReadBigEndianUInt<8>()), uint8_t>::value));
+
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<64>()),
+                              uint64_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<48>()),
+                              uint64_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<32>()),
+                              uint32_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<16>()),
+                              uint16_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.ReadLittleEndianUInt<8>()),
+                              uint8_t>::value));
+
+  EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<64>()),
+                              uint64_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<48>()),
+                              uint64_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<32>()),
+                              uint32_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<16>()),
+                              uint16_t>::value));
+  EXPECT_TRUE((::std::is_same<decltype(buffer.UncheckedReadBigEndianUInt<8>()),
+                              uint8_t>::value));
+
+  EXPECT_TRUE(
+      (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<64>()),
+                      uint64_t>::value));
+  EXPECT_TRUE(
+      (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<48>()),
+                      uint64_t>::value));
+  EXPECT_TRUE(
+      (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<32>()),
+                      uint32_t>::value));
+  EXPECT_TRUE(
+      (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<16>()),
+                      uint16_t>::value));
+  EXPECT_TRUE(
+      (::std::is_same<decltype(buffer.UncheckedReadLittleEndianUInt<8>()),
+                      uint8_t>::value));
+}
+
+TEST(ReadOnlyContiguousBuffer, Methods) {
+  const ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b,
+                                         0x0a, 0x09, 0x08, 0x07, 0x06, 0x05,
+                                         0x04, 0x03, 0x02, 0x01}};
+  const auto buffer = ReadOnlyContiguousBuffer{bytes.data(), bytes.size() - 4};
+  EXPECT_DEATH(buffer.ReadBigEndianUInt<64>(), "");
+  EXPECT_TRUE(buffer.Ok());
+  EXPECT_EQ(bytes.size() - 4, buffer.SizeInBytes());
+  EXPECT_EQ(0x100f0e0d0c0b0a09, buffer.UncheckedReadBigEndianUInt<64>());
+  EXPECT_EQ(0x090a0b0c0d0e0f10, buffer.UncheckedReadLittleEndianUInt<64>());
+
+  const auto offset_buffer = buffer.GetOffsetStorage<1, 0>(4, 4);
+  EXPECT_EQ(0x0c0b0a09, offset_buffer.ReadBigEndianUInt<32>());
+  EXPECT_EQ(0x090a0b0c, offset_buffer.ReadLittleEndianUInt<32>());
+  EXPECT_EQ(0x0c0b0a0908070605, offset_buffer.UncheckedReadBigEndianUInt<64>());
+  EXPECT_EQ(4, offset_buffer.SizeInBytes());
+  EXPECT_TRUE(offset_buffer.Ok());
+
+  const auto small_offset_buffer = buffer.GetOffsetStorage<1, 0>(4, 1);
+  EXPECT_EQ(0x0c, small_offset_buffer.ReadBigEndianUInt<8>());
+  EXPECT_EQ(0x0c, small_offset_buffer.ReadLittleEndianUInt<8>());
+  EXPECT_EQ(1, small_offset_buffer.SizeInBytes());
+  EXPECT_TRUE(small_offset_buffer.Ok());
+
+  EXPECT_FALSE(ReadOnlyContiguousBuffer().Ok());
+  EXPECT_FALSE(
+      (ReadOnlyContiguousBuffer{static_cast<char *>(nullptr), 12}.Ok()));
+  EXPECT_DEATH((ReadOnlyContiguousBuffer{static_cast<char *>(nullptr), 4}
+                    .ReadBigEndianUInt<32>()),
+               "");
+  EXPECT_EQ(0, ReadOnlyContiguousBuffer().SizeInBytes());
+  EXPECT_EQ(0, (ReadOnlyContiguousBuffer{static_cast<char *>(nullptr), 12}
+                    .SizeInBytes()));
+  EXPECT_DEATH(
+      (ReadOnlyContiguousBuffer{bytes.data(), 0}.ReadBigEndianUInt<8>()), "");
+
+  // The size of the resulting buffer should be the minimum of the available
+  // size and the requested size.
+  EXPECT_EQ(bytes.size() - 8,
+            (buffer.GetOffsetStorage<1, 0>(4, bytes.size() - 4).SizeInBytes()));
+  EXPECT_EQ(4, (buffer.GetOffsetStorage<1, 0>(0, 4).SizeInBytes()));
+  EXPECT_EQ(0, (buffer.GetOffsetStorage<1, 0>(bytes.size(), 4).SizeInBytes()));
+  EXPECT_FALSE((ReadOnlyContiguousBuffer().GetOffsetStorage<1, 0>(0, 0).Ok()));
+}
+
+TEST(ReadWriteContiguousBuffer, Methods) {
+  ::std::vector<uint8_t> bytes = {
+      {0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}};
+  const auto buffer = ReadWriteContiguousBuffer{bytes.data(), bytes.size() - 4};
+  // Read and Ok methods should work just as in ReadOnlyContiguousBuffer.
+  EXPECT_TRUE(buffer.Ok());
+  EXPECT_EQ(bytes.size() - 4, buffer.SizeInBytes());
+  EXPECT_EQ(0x0c0b0a0908070605, buffer.ReadBigEndianUInt<64>());
+
+  buffer.WriteBigEndianUInt<64>(0x05060708090a0b0c);
+  EXPECT_EQ((::std::vector<uint8_t>{0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+                                    0x0c, 0x04, 0x03, 0x02, 0x01}),
+            bytes);
+  buffer.WriteLittleEndianUInt<64>(0x05060708090a0b0c);
+  EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06,
+                                    0x05, 0x04, 0x03, 0x02, 0x01}),
+            bytes);
+
+  const auto offset_buffer = buffer.GetOffsetStorage<1, 0>(4, 4);
+  offset_buffer.WriteBigEndianUInt<32>(0x05060708);
+  EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x05, 0x06, 0x07,
+                                    0x08, 0x04, 0x03, 0x02, 0x01}),
+            bytes);
+  offset_buffer.WriteLittleEndianUInt<32>(0x05060708);
+  EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06,
+                                    0x05, 0x04, 0x03, 0x02, 0x01}),
+            bytes);
+
+  const auto small_offset_buffer = buffer.GetOffsetStorage<1, 0>(4, 1);
+  small_offset_buffer.WriteBigEndianUInt<8>(0x80);
+  EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x80, 0x07, 0x06,
+                                    0x05, 0x04, 0x03, 0x02, 0x01}),
+            bytes);
+  small_offset_buffer.WriteLittleEndianUInt<8>(0x08);
+  EXPECT_EQ((::std::vector<uint8_t>{0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06,
+                                    0x05, 0x04, 0x03, 0x02, 0x01}),
+            bytes);
+
+  EXPECT_DEATH(ReadWriteContiguousBuffer().ReadLittleEndianUInt<8>(), "");
+  EXPECT_DEATH(
+      (ReadWriteContiguousBuffer{static_cast<unsigned char *>(nullptr), 1}
+           .ReadLittleEndianUInt<8>()),
+      "");
+  EXPECT_DEATH(
+      (ReadWriteContiguousBuffer{static_cast<unsigned char *>(nullptr), 1}
+           .WriteLittleEndianUInt<8>(0xff)),
+      "");
+}
+
+TEST(ContiguousBuffer, AssignmentFromCompatibleContiguousBuffers) {
+  alignas(4) char data[8];
+  ContiguousBuffer<const unsigned char, 1, 0> buffer;
+  buffer = ContiguousBuffer<char, 4, 1>(data + 1, sizeof data - 1);
+  EXPECT_TRUE(buffer.Ok());
+  EXPECT_EQ(buffer.data(), reinterpret_cast<unsigned char *>(data + 1));
+
+  ContiguousBuffer<const signed char, 2, 1> aligned_buffer;
+  aligned_buffer =
+      ContiguousBuffer<unsigned char, 4, 3>(data + 3, sizeof data - 3);
+  EXPECT_TRUE(aligned_buffer.Ok());
+  EXPECT_EQ(aligned_buffer.data(), reinterpret_cast<signed char *>(data + 3));
+}
+
+TEST(ContiguousBuffer, ConstructionFromCompatibleContiguousBuffers) {
+  alignas(4) char data[8];
+  ContiguousBuffer<const unsigned char, 1, 0> buffer{
+      ContiguousBuffer<char, 4, 1>(data + 1, sizeof data - 1)};
+  EXPECT_TRUE(buffer.Ok());
+  EXPECT_EQ(buffer.data(), reinterpret_cast<unsigned char *>(data + 1));
+
+  ContiguousBuffer<const signed char, 2, 1> aligned_buffer{
+      ContiguousBuffer<unsigned char, 4, 3>(data + 3, sizeof data - 3)};
+  EXPECT_TRUE(aligned_buffer.Ok());
+  EXPECT_EQ(aligned_buffer.data(), reinterpret_cast<signed char *>(data + 3));
+}
+
+TEST(LittleEndianByteOrderer, Methods) {
+  ::std::vector<uint8_t> bytes = {{21, 22, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24}};
+  const int buffer_start = 2;
+  const auto buffer = LittleEndianByteOrderer<ReadWriteContiguousBuffer>{
+      ReadWriteContiguousBuffer{bytes.data() + buffer_start, 8}};
+  EXPECT_EQ(8, buffer.SizeInBytes());
+  EXPECT_TRUE(buffer.Ok());
+  EXPECT_EQ(0x0807060504030201, buffer.ReadUInt<64>());
+  EXPECT_EQ(0x0807060504030201, buffer.UncheckedReadUInt<64>());
+  EXPECT_DEATH(buffer.ReadUInt<56>(), "");
+  EXPECT_EQ(0x07060504030201, buffer.UncheckedReadUInt<56>());
+  buffer.WriteUInt<64>(0x0102030405060708);
+  EXPECT_EQ((::std::vector<uint8_t>{21, 22, 8, 7, 6, 5, 4, 3, 2, 1, 23, 24}),
+            bytes);
+  buffer.UncheckedWriteUInt<64>(0x0807060504030201);
+  EXPECT_EQ((::std::vector<uint8_t>{21, 22, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24}),
+            bytes);
+  EXPECT_DEATH(buffer.WriteUInt<56>(0x77777777777777), "");
+
+  EXPECT_FALSE(LittleEndianByteOrderer<ReadOnlyContiguousBuffer>().Ok());
+  EXPECT_EQ(0,
+            LittleEndianByteOrderer<ReadOnlyContiguousBuffer>().SizeInBytes());
+  EXPECT_EQ(bytes[1], (LittleEndianByteOrderer<ReadOnlyContiguousBuffer>{
+                          ReadOnlyContiguousBuffer{bytes.data() + 1, 0}}
+                           .UncheckedReadUInt<8>()));
+  EXPECT_TRUE((LittleEndianByteOrderer<ReadOnlyContiguousBuffer>{
+      ReadOnlyContiguousBuffer{bytes.data(), 0}}
+                   .Ok()));
+}
+
+TEST(BigEndianByteOrderer, Methods) {
+  ::std::vector<uint8_t> bytes = {{21, 22, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24}};
+  const int buffer_start = 2;
+  const auto buffer = BigEndianByteOrderer<ReadWriteContiguousBuffer>{
+      ReadWriteContiguousBuffer{bytes.data() + buffer_start, 8}};
+  EXPECT_EQ(8, buffer.SizeInBytes());
+  EXPECT_TRUE(buffer.Ok());
+  EXPECT_EQ(0x0102030405060708, buffer.ReadUInt<64>());
+  EXPECT_EQ(0x0102030405060708, buffer.UncheckedReadUInt<64>());
+  EXPECT_DEATH(buffer.ReadUInt<56>(), "");
+  EXPECT_EQ(0x01020304050607, buffer.UncheckedReadUInt<56>());
+  buffer.WriteUInt<64>(0x0807060504030201);
+  EXPECT_EQ((::std::vector<uint8_t>{21, 22, 8, 7, 6, 5, 4, 3, 2, 1, 23, 24}),
+            bytes);
+  buffer.UncheckedWriteUInt<64>(0x0102030405060708);
+  EXPECT_EQ((::std::vector<uint8_t>{21, 22, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24}),
+            bytes);
+  EXPECT_DEATH(buffer.WriteUInt<56>(0x77777777777777), "");
+
+  EXPECT_FALSE(BigEndianByteOrderer<ReadOnlyContiguousBuffer>().Ok());
+  EXPECT_EQ(0, BigEndianByteOrderer<ReadOnlyContiguousBuffer>().SizeInBytes());
+  EXPECT_EQ(bytes[1], (BigEndianByteOrderer<ReadOnlyContiguousBuffer>{
+                          ReadOnlyContiguousBuffer{bytes.data() + 1, 0}}
+                           .UncheckedReadUInt<8>()));
+  EXPECT_TRUE((BigEndianByteOrderer<ReadOnlyContiguousBuffer>{
+      ReadOnlyContiguousBuffer{bytes.data(), 0}}
+                   .Ok()));
+}
+
+TEST(NullByteOrderer, Methods) {
+  uint8_t bytes[] = {0xdb, 0x0f, 0x0e, 0x0d};
+  const auto buffer = NullByteOrderer<ReadWriteContiguousBuffer>{
+      ReadWriteContiguousBuffer{bytes, 1}};
+  EXPECT_EQ(bytes[0], buffer.ReadUInt<8>());
+  EXPECT_EQ(bytes[0], buffer.UncheckedReadUInt<8>());
+  // NullByteOrderer::UncheckedRead ignores its argument.
+  EXPECT_EQ(bytes[0], buffer.UncheckedReadUInt<8>());
+  buffer.WriteUInt<8>(0x24);
+  EXPECT_EQ(0x24, bytes[0]);
+  buffer.UncheckedWriteUInt<8>(0x25);
+  EXPECT_EQ(0x25, bytes[0]);
+  EXPECT_EQ(1, buffer.SizeInBytes());
+  EXPECT_TRUE(buffer.Ok());
+
+  EXPECT_FALSE(NullByteOrderer<ReadOnlyContiguousBuffer>().Ok());
+  EXPECT_EQ(0, NullByteOrderer<ReadOnlyContiguousBuffer>().SizeInBytes());
+  EXPECT_DEATH((NullByteOrderer<ReadOnlyContiguousBuffer>{
+                   ReadOnlyContiguousBuffer{bytes, 0}}
+                    .ReadUInt<8>()),
+               "");
+  EXPECT_DEATH((NullByteOrderer<ReadOnlyContiguousBuffer>{
+                   ReadOnlyContiguousBuffer{bytes, 2}}
+                    .ReadUInt<8>()),
+               "");
+  EXPECT_EQ(bytes[0], (NullByteOrderer<ReadOnlyContiguousBuffer>{
+                          ReadOnlyContiguousBuffer{bytes, 0}}
+                           .UncheckedReadUInt<8>()));
+  EXPECT_TRUE((NullByteOrderer<ReadOnlyContiguousBuffer>{
+      ReadOnlyContiguousBuffer{bytes, 0}}
+                   .Ok()));
+}
+
+TEST(BitBlock, BigEndianMethods) {
+  uint8_t bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+                     0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
+  const auto big_endian =
+      BigEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes + 4, 8}};
+  EXPECT_EQ(64, big_endian.SizeInBits());
+  EXPECT_TRUE(big_endian.Ok());
+  EXPECT_EQ(0x05060708090a0b0cUL, big_endian.ReadUInt());
+  EXPECT_EQ(0x05060708090a0b0cUL, big_endian.UncheckedReadUInt());
+  EXPECT_FALSE(BigEndianBitBlockN<64>().Ok());
+  EXPECT_EQ(64, BigEndianBitBlockN<64>().SizeInBits());
+  EXPECT_FALSE(
+      (BigEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes, 0}}.Ok()));
+}
+
+TEST(BitBlock, LittleEndianMethods) {
+  uint8_t bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+                     0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
+  const auto little_endian =
+      LittleEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes + 4, 8}};
+  EXPECT_EQ(64, little_endian.SizeInBits());
+  EXPECT_TRUE(little_endian.Ok());
+  EXPECT_EQ(0x0c0b0a0908070605UL, little_endian.ReadUInt());
+  EXPECT_EQ(0x0c0b0a0908070605UL, little_endian.UncheckedReadUInt());
+  EXPECT_FALSE(LittleEndianBitBlockN<64>().Ok());
+  EXPECT_EQ(64, LittleEndianBitBlockN<64>().SizeInBits());
+  EXPECT_FALSE(
+      (LittleEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes, 0}}.Ok()));
+}
+
+TEST(BitBlock, GetOffsetStorage) {
+  uint8_t bytes[] = {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09,
+                     0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
+  const auto bit_block =
+      LittleEndianBitBlockN<64>{ReadWriteContiguousBuffer{bytes, 8}};
+  const OffsetBitBlock<LittleEndianBitBlockN<64>> offset_block =
+      bit_block.GetOffsetStorage<1, 0>(4, 8);
+  EXPECT_EQ(8, offset_block.SizeInBits());
+  EXPECT_EQ(0xf1, offset_block.ReadUInt());
+  EXPECT_EQ(bit_block.SizeInBits(),
+            (bit_block.GetOffsetStorage<1, 0>(8, bit_block.SizeInBits())
+                 .SizeInBits()));
+  EXPECT_FALSE(
+      (bit_block.GetOffsetStorage<1, 0>(8, bit_block.SizeInBits()).Ok()));
+  EXPECT_EQ(10, (bit_block.GetOffsetStorage<1, 0>(bit_block.SizeInBits(), 10)
+                     .SizeInBits()));
+}
+
+TEST(OffsetBitBlock, Methods) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09}};
+  const auto bit_block =
+      LittleEndianBitBlockN<64>{ReadWriteContiguousBuffer{&bytes}};
+  EXPECT_FALSE((bit_block.GetOffsetStorage<1, 0>(0, 96).Ok()));
+  EXPECT_TRUE((bit_block.GetOffsetStorage<1, 0>(0, 64).Ok()));
+
+  const auto offset_block = bit_block.GetOffsetStorage<1, 0>(8, 48);
+  EXPECT_FALSE((offset_block.GetOffsetStorage<1, 0>(40, 16).Ok()));
+  EXPECT_EQ(0x0a0b0c0d0e0f, offset_block.ReadUInt());
+  EXPECT_EQ(0x0a0b0c0d0e0f, offset_block.UncheckedReadUInt());
+  offset_block.WriteUInt(0x0f0e0d0c0b0a);
+  EXPECT_EQ(
+      (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x09}),
+      bytes);
+  offset_block.UncheckedWriteUInt(0x0a0b0c0d0e0f);
+  EXPECT_EQ(
+      (::std::vector<uint8_t>{0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09}),
+      bytes);
+  EXPECT_DEATH(offset_block.WriteUInt(0x10f0e0d0c0b0a), "");
+  offset_block.UncheckedWriteUInt(0x10f0e0d0c0b0a);
+  EXPECT_EQ(
+      (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x09}),
+      bytes);
+
+  const auto offset_offset_block = offset_block.GetOffsetStorage<1, 0>(16, 16);
+  EXPECT_FALSE((offset_offset_block.GetOffsetStorage<1, 0>(8, 16).Ok()));
+  EXPECT_EQ(0x0d0c, offset_offset_block.ReadUInt());
+  EXPECT_EQ(0x0d0c, offset_offset_block.UncheckedReadUInt());
+  offset_offset_block.WriteUInt(0x0c0d);
+  EXPECT_EQ(
+      (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0d, 0x0c, 0x0e, 0x0f, 0x09}),
+      bytes);
+  offset_offset_block.UncheckedWriteUInt(0x0d0c);
+  EXPECT_EQ(
+      (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x09}),
+      bytes);
+  EXPECT_DEATH(offset_offset_block.WriteUInt(0x10c0d), "");
+  offset_offset_block.UncheckedWriteUInt(0x20c0d);
+  EXPECT_EQ(
+      (::std::vector<uint8_t>{0x10, 0x0a, 0x0b, 0x0d, 0x0c, 0x0e, 0x0f, 0x09}),
+      bytes);
+
+  const auto null_offset_block = OffsetBitBlock<BigEndianBitBlockN<32>>();
+  EXPECT_FALSE(null_offset_block.Ok());
+  EXPECT_EQ(0, null_offset_block.SizeInBits());
+}
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_prelude.h b/public/emboss_prelude.h
new file mode 100644
index 0000000..85eeba8
--- /dev/null
+++ b/public/emboss_prelude.h
@@ -0,0 +1,803 @@
+// 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.
+
+// This header contains implementations of the types in the Emboss Prelude
+// (UInt, Int, Flag, etc.)
+#ifndef EMBOSS_PUBLIC_EMBOSS_PRELUDE_H_
+#define EMBOSS_PUBLIC_EMBOSS_PRELUDE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <limits>
+#include <type_traits>
+#include <utility>
+
+#include "public/emboss_cpp_util.h"
+
+// This namespace must match the [(cpp) namespace] in the Emboss prelude.
+namespace emboss {
+namespace prelude {
+
+// FlagView is the C++ implementation of the Emboss "Flag" type, which is a
+// 1-bit value.
+template <class Parameters, class BitBlock>
+class FlagView final {
+ public:
+  static_assert(Parameters::kBits == 1, "FlagView must be 1 bit.");
+
+  explicit FlagView(BitBlock bits) : bit_block_{bits} {}
+  FlagView() : bit_block_() {}
+  FlagView(const FlagView &) = default;
+  FlagView(FlagView &&) = default;
+  FlagView &operator=(const FlagView &) = default;
+  FlagView &operator=(FlagView &&) = default;
+  ~FlagView() = default;
+
+  bool Read() const {
+    bool result = bit_block_.ReadUInt();
+    EMBOSS_CHECK(Parameters::ValueIsOk(result));
+    return result;
+  }
+  bool UncheckedRead() const { return bit_block_.UncheckedReadUInt(); }
+  void Write(bool value) const { EMBOSS_CHECK(TryToWrite(value)); }
+  bool TryToWrite(bool value) const {
+    if (!CouldWriteValue(value)) return false;
+    if (!IsComplete()) return false;
+    bit_block_.WriteUInt(value);
+    return true;
+  }
+  static constexpr bool CouldWriteValue(bool value) {
+    return Parameters::ValueIsOk(value);
+  }
+  void UncheckedWrite(bool value) const {
+    bit_block_.UncheckedWriteUInt(value);
+  }
+
+  template <typename OtherView>
+  void CopyFrom(const OtherView &other) const {
+    Write(other.Read());
+  }
+  template <typename OtherView>
+  void UncheckedCopyFrom(const OtherView &other) const {
+    UncheckedWrite(other.UncheckedRead());
+  }
+  template <typename OtherView>
+  bool TryToCopyFrom(const OtherView &other) const {
+    return TryToWrite(other.Read());
+  }
+
+  bool Ok() const {
+    return IsComplete() && Parameters::ValueIsOk(UncheckedRead());
+  }
+  template <class OtherBitBlock>
+  bool Equals(const FlagView<Parameters, OtherBitBlock> &other) const {
+    return Read() == other.Read();
+  }
+  template <class OtherBitBlock>
+  bool UncheckedEquals(const FlagView<Parameters, OtherBitBlock> &other) const {
+    return UncheckedRead() == other.UncheckedRead();
+  }
+  bool IsComplete() const {
+    return bit_block_.Ok() && bit_block_.SizeInBits() > 0;
+  }
+
+  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;
+  }
+
+  template <class Stream>
+  void WriteToTextStream(Stream *stream,
+                         const ::emboss::TextOutputOptions &options) const {
+    ::emboss::support::WriteBooleanViewToTextStream(this, stream, options);
+  }
+
+ private:
+  BitBlock bit_block_;
+};
+
+// UIntView is a view for UInts inside of bitfields.
+template <class Parameters, class BitViewType>
+class UIntView final {
+ public:
+  using ValueType = typename ::emboss::support::LeastWidthInteger<
+      Parameters::kBits>::Unsigned;
+
+  static_assert(
+      Parameters::kBits <= sizeof(ValueType) * 8,
+      "UIntView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
+
+  template <typename... Args>
+  explicit UIntView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+  UIntView() : buffer_() {}
+  UIntView(const UIntView &) = default;
+  UIntView(UIntView &&) = default;
+  UIntView &operator=(const UIntView &) = default;
+  UIntView &operator=(UIntView &&) = default;
+  ~UIntView() = default;
+
+  ValueType Read() const {
+    ValueType result = buffer_.ReadUInt();
+    EMBOSS_CHECK(Parameters::ValueIsOk(result));
+    return result;
+  }
+  ValueType UncheckedRead() const { return buffer_.UncheckedReadUInt(); }
+
+  // The Write, TryToWrite, and CouldWriteValue methods are templated in order
+  // to avoid surprises due to implicit narrowing.
+  //
+  // In C++, you can pass (say) an `int` to a function expecting `uint8_t`, and
+  // the compiler will silently cast the `int` to `uint8_t`, which can change
+  // the value.  Even with fairly aggressive warnings, something like this will
+  // silently compile, and print `256 is not >= 128!`:
+  //
+  //    bool is_big_uint8(uint8_t value) { return value >= 128; }
+  //    bool is_big(uint32_t value) { return is_big_uint8(value); }
+  //    int main() {
+  //        assert(!is_big(256));  // big is truncated to 0.
+  //        std::cout << 256 << " is not >= 128!\n";
+  //        return 0;
+  //    }
+  //
+  // (Most compilers will give a warning when directly passing a *constant* that
+  // gets truncated; for example, GCC will throw -Woverflow on
+  // `is_big_uint8(256U)`.)
+  template <typename IntT,
+            typename = typename ::std::enable_if<
+                (::std::numeric_limits<typename ::std::remove_cv<
+                     typename ::std::remove_reference<IntT>::type>::type>::
+                     is_integer &&
+                 !::std::is_same<bool, typename ::std::remove_cv<
+                                           typename ::std::remove_reference<
+                                               IntT>::type>::type>::value) ||
+                ::std::is_enum<IntT>::value>::type>
+  void Write(IntT value) const {
+    EMBOSS_CHECK(TryToWrite(value));
+  }
+
+  template <typename IntT,
+            typename = typename ::std::enable_if<
+                (::std::numeric_limits<typename ::std::remove_cv<
+                     typename ::std::remove_reference<IntT>::type>::type>::
+                     is_integer &&
+                 !::std::is_same<bool, typename ::std::remove_cv<
+                                           typename ::std::remove_reference<
+                                               IntT>::type>::type>::value) ||
+                ::std::is_enum<IntT>::value>::type>
+  bool TryToWrite(IntT value) const {
+    if (!CouldWriteValue(value)) return false;
+    if (!IsComplete()) return false;
+    buffer_.WriteUInt(value);
+    return true;
+  }
+
+  template <typename IntT,
+            typename = typename ::std::enable_if<
+                (::std::numeric_limits<typename ::std::remove_cv<
+                     typename ::std::remove_reference<IntT>::type>::type>::
+                     is_integer &&
+                 !::std::is_same<bool, typename ::std::remove_cv<
+                                           typename ::std::remove_reference<
+                                               IntT>::type>::type>::value) ||
+                ::std::is_enum<IntT>::value>::type>
+  static constexpr bool CouldWriteValue(IntT value) {
+    // Implicit conversions are doing some work here, but the upshot is that the
+    // value must be at least 0, and at most (2**kBits)-1.  The clause to
+    // compute (2**kBits)-1 should not be "simplified" further.
+    //
+    // Because of C++ implicit integer promotions, the (2**kBits)-1 computation
+    // works differently when `ValueType` is smaller than `unsigned int` than it
+    // does when `ValueType` is at least as big as `unsigned int`.
+    //
+    // For example, when `ValueType` is `uint8_t` and `kBits` is 8:
+    //
+    // 1.   `static_cast<ValueType>(1)` becomes `uint8_t(1)`.
+    // 2.   `uint8_t(1) << (kBits - 1)` is `uint8_t(1) << 7`.
+    // 3.   The shift operator `<<` promotes its left operand to `unsigned`,
+    //      giving `unsigned(1) << 7`.
+    // 4.   `unsigned(1) << 7` becomes `unsigned(0x80)`.
+    // 5.   `unsigned(0x80) << 1` becomes `unsigned(0x100)`.
+    // 6.   Finally, `unsigned(0x100) - 1` is `unsigned(0xff)`.
+    //
+    // (Note that the cases where `kBits` is less than `sizeof(ValueType) * 8`
+    // are very similar.)
+    //
+    // When `ValueType` is `uint32_t`, `unsigned` is 32 bits, and `kBits` is 32:
+    //
+    // 1.   `static_cast<ValueType>(1)` becomes `uint32_t(1)`.
+    // 2.   `uint32_t(1) << (kBits - 1)` is `uint32_t(1) << 31`.
+    // 3.   The shift operator `<<` does *not* further promote `uint32_t`.
+    // 4.   `uint32_t(1) << 31` becomes `uint32_t(0x80000000)`.  Note that
+    //      `uint32_t(1) << 32` would be undefined behavior (shift of >= the
+    //      size of the left operand type), which is why the shift is broken
+    //      into two parts.
+    // 5.   `uint32_t(0x80000000) << 1` overflows, leaving `uint32_t(0)`.
+    // 6.   `uint32_t(0) - 1` underflows, leaving `uint32_t(0xffffffff)`.
+    //
+    // Because unsigned overflow and underflow are defined to be modulo 2**N,
+    // where N is the number of bits in the type, this is entirely
+    // standards-compliant.
+    return value >= 0 &&
+           static_cast</**/ ::std::uint64_t>(value) <=
+               ((static_cast<ValueType>(1) << (Parameters::kBits - 1)) << 1) -
+                   1 &&
+           Parameters::ValueIsOk(value);
+  }
+  void UncheckedWrite(ValueType value) const {
+    buffer_.UncheckedWriteUInt(value);
+  }
+
+  template <typename OtherView>
+  void CopyFrom(const OtherView &other) const {
+    Write(other.Read());
+  }
+  template <typename OtherView>
+  void UncheckedCopyFrom(const OtherView &other) const {
+    UncheckedWrite(other.UncheckedRead());
+  }
+  template <typename OtherView>
+  bool TryToCopyFrom(const OtherView &other) const {
+    return other.Ok() && TryToWrite(other.Read());
+  }
+
+  // All bit patterns in the underlying buffer are valid, so Ok() is always
+  // true if IsComplete() is true.
+  bool Ok() const {
+    return IsComplete() && Parameters::ValueIsOk(UncheckedRead());
+  }
+  template <class OtherBitViewType>
+  bool Equals(const UIntView<Parameters, OtherBitViewType> &other) const {
+    return Read() == other.Read();
+  }
+  template <class OtherBitViewType>
+  bool UncheckedEquals(
+      const UIntView<Parameters, OtherBitViewType> &other) const {
+    return UncheckedRead() == other.UncheckedRead();
+  }
+  bool IsComplete() const {
+    return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
+  }
+
+  template <class Stream>
+  bool UpdateFromTextStream(Stream *stream) const {
+    return support::ReadIntegerFromTextStream(this, stream);
+  }
+
+  template <class Stream>
+  void WriteToTextStream(Stream *stream,
+                         ::emboss::TextOutputOptions options) const {
+    support::WriteIntegerViewToTextStream(this, stream, options);
+  }
+
+  static constexpr int SizeInBits() { return Parameters::kBits; }
+
+ private:
+  BitViewType buffer_;
+};
+
+// IntView is a view for Ints inside of bitfields.
+template <class Parameters, class BitViewType>
+class IntView final {
+ public:
+  using ValueType =
+      typename ::emboss::support::LeastWidthInteger<Parameters::kBits>::Signed;
+
+  static_assert(Parameters::kBits <= sizeof(ValueType) * 8,
+                "IntView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
+
+  template <typename... Args>
+  explicit IntView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+  IntView() : buffer_() {}
+  IntView(const IntView &) = default;
+  IntView(IntView &&) = default;
+  IntView &operator=(const IntView &) = default;
+  IntView &operator=(IntView &&) = default;
+  ~IntView() = default;
+
+  ValueType Read() const {
+    ValueType value = ConvertToSigned(buffer_.ReadUInt());
+    EMBOSS_CHECK(Parameters::ValueIsOk(value));
+    return value;
+  }
+  ValueType UncheckedRead() const {
+    return ConvertToSigned(buffer_.UncheckedReadUInt());
+  }
+  // As with UIntView, above, Write, TryToWrite, and CouldWriteValue need to be
+  // templated in order to avoid surprises due to implicit narrowing
+  // conversions.
+  template <typename IntT,
+            typename = typename ::std::enable_if<
+                (::std::numeric_limits<typename ::std::remove_cv<
+                     typename ::std::remove_reference<IntT>::type>::type>::
+                     is_integer &&
+                 !::std::is_same<bool, typename ::std::remove_cv<
+                                           typename ::std::remove_reference<
+                                               IntT>::type>::type>::value) ||
+                ::std::is_enum<IntT>::value>::type>
+  void Write(IntT value) const {
+    EMBOSS_CHECK(TryToWrite(value));
+  }
+
+  template <typename IntT,
+            typename = typename ::std::enable_if<
+                (::std::numeric_limits<typename ::std::remove_cv<
+                     typename ::std::remove_reference<IntT>::type>::type>::
+                     is_integer &&
+                 !::std::is_same<bool, typename ::std::remove_cv<
+                                           typename ::std::remove_reference<
+                                               IntT>::type>::type>::value) ||
+                ::std::is_enum<IntT>::value>::type>
+  bool TryToWrite(IntT value) const {
+    if (!CouldWriteValue(value)) return false;
+    if (!IsComplete()) return false;
+    buffer_.WriteUInt(::emboss::support::MaskToNBits(
+        static_cast<typename BitViewType::ValueType>(value),
+        Parameters::kBits));
+    return true;
+  }
+
+  template <typename IntT,
+            typename = typename ::std::enable_if<
+                (::std::numeric_limits<typename ::std::remove_cv<
+                     typename ::std::remove_reference<IntT>::type>::type>::
+                     is_integer &&
+                 !::std::is_same<bool, typename ::std::remove_cv<
+                                           typename ::std::remove_reference<
+                                               IntT>::type>::type>::value) ||
+                ::std::is_enum<IntT>::value>::type>
+  static constexpr bool CouldWriteValue(IntT value) {
+    // This effectively checks that value >= -(2**(kBits-1) and value <=
+    // (2**(kBits-1))-1.
+    //
+    // This has to be done somewhat piecemeal, in order to avoid various bits of
+    // undefined and implementation-defined behavior.
+    //
+    // First, if IntT is an unsigned type, the check that value >=
+    // -(2**(kBits-1)) is skipped, in order to avoid any signed <-> unsigned
+    // conversions.
+    //
+    // Second, if kBits is 1, then the limits -1 and 0 are explicit, so that
+    // there is never a shift by -1 (which is undefined behavior).
+    //
+    // Third, the shifts are by (kBits - 2), so that they do not alter sign
+    // bits.  To get the final bounds, we use a bit of addition and
+    // multiplication.  For example, for 8 bits, the lower bound is (1 << 6) *
+    // -2, which is 64 * -2, which is -128.  The corresponding upper bound is
+    // ((1 << 6) - 1) * 2 + 1, which is (64 - 1) * 2 + 1, which is 63 * 2 + 1,
+    // which is 126 + 1, which is 127.  The upper bound must be computed in
+    // multiple steps like this in order to avoid overflow.
+    return (!::std::is_signed<typename ::std::remove_cv<
+                typename ::std::remove_reference<IntT>::type>::type>::value ||
+            static_cast</**/ ::std::int64_t>(value) >=
+                (Parameters::kBits == 1
+                     ? -1
+                     : (static_cast<ValueType>(1) << (Parameters::kBits - 2)) *
+                           -2)) &&
+           value <=
+               (Parameters::kBits == 1
+                    ? 0
+                    : ((static_cast<ValueType>(1) << (Parameters::kBits - 2)) -
+                       1) * 2 +
+                          1) &&
+           Parameters::ValueIsOk(value);
+  }
+
+  void UncheckedWrite(ValueType value) const {
+    buffer_.UncheckedWriteUInt(::emboss::support::MaskToNBits(
+        static_cast<typename BitViewType::ValueType>(value),
+        Parameters::kBits));
+  }
+
+  template <typename OtherView>
+  void CopyFrom(const OtherView &other) const {
+    Write(other.Read());
+  }
+  template <typename OtherView>
+  void UncheckedCopyFrom(const IntView &other) const {
+    UncheckedWrite(other.UncheckedRead());
+  }
+  template <typename OtherView>
+  bool TryToCopyFrom(const OtherView &other) const {
+    return other.Ok() && TryToWrite(other.Read());
+  }
+
+  // All bit patterns in the underlying buffer are valid, so Ok() is always
+  // true if IsComplete() is true.
+  bool Ok() const {
+    return IsComplete() && Parameters::ValueIsOk(UncheckedRead());
+  }
+  template <class OtherBitViewType>
+  bool Equals(const IntView<Parameters, OtherBitViewType> &other) const {
+    return Read() == other.Read();
+  }
+  template <class OtherBitViewType>
+  bool UncheckedEquals(
+      const IntView<Parameters, OtherBitViewType> &other) const {
+    return UncheckedRead() == other.UncheckedRead();
+  }
+  bool IsComplete() const {
+    return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
+  }
+
+  template <class Stream>
+  bool UpdateFromTextStream(Stream *stream) const {
+    return support::ReadIntegerFromTextStream(this, stream);
+  }
+
+  template <class Stream>
+  void WriteToTextStream(Stream *stream,
+                         ::emboss::TextOutputOptions options) const {
+    support::WriteIntegerViewToTextStream(this, stream, options);
+  }
+
+  static constexpr int SizeInBits() { return Parameters::kBits; }
+
+ private:
+  static ValueType ConvertToSigned(typename BitViewType::ValueType data) {
+    static_assert(sizeof(ValueType) <= sizeof(typename BitViewType::ValueType),
+                  "Integer types wider than BitViewType::ValueType are not "
+                  "supported.");
+#if EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT
+    // static_cast from unsigned to signed is implementation-defined when the
+    // value does not fit in the signed type (in this case, when the final value
+    // should be negative).  Most implementations use a reasonable definition,
+    // so on most systems we can just cast.
+    //
+    // If the integer does not take up the full width of ValueType, it needs to
+    // be sign-extended until it does.  The easiest way to do this is to shift
+    // until the sign bit is in the topmost position, then cast to signed, then
+    // shift back.  The shift back will copy the sign bit.
+    return static_cast<ValueType>(
+               data << (sizeof(ValueType) * 8 - Parameters::kBits)) >>
+           (sizeof(ValueType) * 8 - Parameters::kBits);
+#else
+    // Otherwise, in order to convert without running into
+    // implementation-defined behavior, first mask out the sign bit.  This
+    // results in (final result MOD 2 ** (width of int in bits - 1)).  That
+    // value can be safely converted to the signed ValueType.
+    //
+    // Finally, if the sign bit was set, subtract (2 ** (width of int in bits -
+    // 2)) twice.
+    //
+    // The 1-bit signed integer case must be handled separately, but it is
+    // (fortunately) quite easy to enumerate both possible values.
+    if (Parameters::kBits == 1) {
+      if (data == 0) {
+        return 0;
+      } else if (data == 1) {
+        return -1;
+      } else {
+        EMBOSS_CHECK(false);
+      }
+    } else {
+      typename BitViewType::ValueType sign_bit =
+          static_cast<typename BitViewType::ValueType>(1)
+          << (Parameters::kBits - 1);
+      typename BitViewType::ValueType mask = sign_bit - 1;
+      typename BitViewType::ValueType data_mod2_to_n = mask & data;
+      ValueType result_sign_bit =
+          static_cast<ValueType>((data & sign_bit) >> 1);
+      return data_mod2_to_n - result_sign_bit - result_sign_bit;
+    }
+#endif
+  }
+
+  BitViewType buffer_;
+};
+
+// The maximum Binary-Coded Decimal (BCD) value that fits in a particular number
+// of bits.
+template <typename ValueType>
+constexpr inline ValueType MaxBcd(int bits) {
+  return bits < 4 ? (1 << bits) - 1
+                  : 10 * (MaxBcd<ValueType>(bits - 4) + 1) - 1;
+}
+
+template <typename ValueType>
+inline bool IsBcd(ValueType x) {
+  // Adapted from:
+  // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord
+  //
+  // This determines if any nibble has a value greater than 9.  It does
+  // this by treating operations on the n-bit value as parallel operations
+  // on n/4 4-bit values.
+  //
+  // The result is computed in the high bit of each nibble: if any of those
+  // bits is set in the end, then at least one nibble had a value in the
+  // range 10-15.
+  //
+  // The first check is subtle: ~x is equivalent to (nibble = 15 - nibble).
+  // Then, 6 is subtracted from each nibble.  This operation will underflow
+  // if the original value was more than 9, leaving the high bit of the
+  // nibble set.  It will also leave the high bit of the nibble set
+  // (without underflow) if the original value was 0 or 1.
+  //
+  // The second check is just x: the high bit of each nibble in x is set if
+  // that nibble's value is 8-15.
+  //
+  // Thus, the first check leaves the high bit set in any nibble with the
+  // value 0, 1, or 10-15, and the second check leaves the high bit set in
+  // any nibble with the value 8-15.  Bitwise-anding these results, high
+  // bits are only set if the original value was 10-15.
+  //
+  // The underflow condition in the first check can screw up the condition
+  // for nibbles in higher positions than the underflowing nibble.  This
+  // cannot affect the overall boolean result, because the underflow
+  // condition only happens if a nibble was greater than 9, and therefore
+  // *that* nibble's final value will be nonzero, and therefore the whole
+  // result will be nonzero, no matter what happens in the higher-order
+  // nibbles.
+  //
+  // A couple of examples in 16 bit:
+  //
+  // x = 0x09a8
+  // (~0x09a8 - 0x6666) & 0x09a8 & 0x8888
+  // ( 0xf657 - 0x6666) & 0x09a8 & 0x8888
+  //            0x8ff1  & 0x09a8 & 0x8888
+  //                      0x09a0 & 0x8888
+  //                               0x0880  Note the underflow into nibble 2
+  //
+  // x = 0x1289
+  // (~0x1289 - 0x6666) & 0x1289 & 0x8888
+  // ( 0xed76 - 0x6666) & 0x1289 & 0x8888
+  //            0x8710  & 0x1289 & 0x8888
+  //                      0x0200 & 0x8888
+  //                               0x0000
+  static_assert(!::std::is_signed<ValueType>::value,
+                "IsBcd only works on unsigned values.");
+  if (sizeof(ValueType) < sizeof(unsigned)) {
+    // For types with lower integer conversion rank than unsigned int, integer
+    // promotion rules cause many implicit conversions to signed int in the math
+    // below, which makes the math go wrong.  Rather than add a dozen explicit
+    // casts back to ValueType, just do the math as 'unsigned'.
+    return IsBcd<unsigned>(x);
+  } else {
+    return ((~x - (~ValueType{0} / 0xf * 0x6 /* 0x6666...6666 */)) & x &
+            (~ValueType{0} / 0xf * 0x8 /* 0x8888...8888 */)) == 0;
+  }
+}
+
+// Base template for Binary-Coded Decimal (BCD) unsigned integer readers.
+template <class Parameters, class BitViewType>
+class BcdView final {
+ public:
+  using ValueType = typename ::emboss::support::LeastWidthInteger<
+      Parameters::kBits>::Unsigned;
+
+  static_assert(Parameters::kBits <= sizeof(ValueType) * 8,
+                "BcdView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
+
+  template <typename... Args>
+  explicit BcdView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+  BcdView() : buffer_() {}
+  BcdView(const BcdView &) = default;
+  BcdView(BcdView &&) = default;
+  BcdView &operator=(const BcdView &) = default;
+  BcdView &operator=(BcdView &&) = default;
+  ~BcdView() = default;
+
+  ValueType Read() const {
+    EMBOSS_CHECK(Ok());
+    return ConvertToBinary(buffer_.ReadUInt());
+  }
+  ValueType UncheckedRead() const {
+    return ConvertToBinary(buffer_.UncheckedReadUInt());
+  }
+  void Write(ValueType value) const { EMBOSS_CHECK(TryToWrite(value)); }
+  bool TryToWrite(ValueType value) const {
+    if (!CouldWriteValue(value)) return false;
+    if (!IsComplete()) return false;
+    buffer_.WriteUInt(ConvertToBcd(value));
+    return true;
+  }
+  static constexpr bool CouldWriteValue(ValueType value) {
+    return value <= MaxValue() && Parameters::ValueIsOk(value);
+  }
+  void UncheckedWrite(ValueType value) const {
+    buffer_.UncheckedWriteUInt(ConvertToBcd(value));
+  }
+
+  template <class Stream>
+  bool UpdateFromTextStream(Stream *stream) const {
+    return support::ReadIntegerFromTextStream(this, stream);
+  }
+
+  template <class Stream>
+  void WriteToTextStream(Stream *stream,
+                         ::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
+    // values of other numeric types in hex or binary.  It seems like there
+    // could be some fancy C++ trickery to allow separate options for separate
+    // view types.
+    support::WriteIntegerViewToTextStream(this, stream, options);
+  }
+
+  template <typename OtherView>
+  void CopyFrom(const OtherView &other) const {
+    Write(other.Read());
+  }
+  template <typename OtherView>
+  void UncheckedCopyFrom(const OtherView &other) const {
+    UncheckedWrite(other.UncheckedRead());
+  }
+  template <typename OtherView>
+  bool TryToCopyFrom(const OtherView &other) const {
+    return other.Ok() && TryToWrite(other.Read());
+  }
+
+  bool Ok() const {
+    if (!IsComplete()) return false;
+    if (!IsBcd(buffer_.ReadUInt())) return false;
+    if (!Parameters::ValueIsOk(UncheckedRead())) return false;
+    return true;
+  }
+  template <class OtherBitViewType>
+  bool Equals(const BcdView<Parameters, OtherBitViewType> &other) const {
+    return Read() == other.Read();
+  }
+  template <class OtherBitViewType>
+  bool UncheckedEquals(
+      const BcdView<Parameters, OtherBitViewType> &other) const {
+    return UncheckedRead() == other.UncheckedRead();
+  }
+  bool IsComplete() const {
+    return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
+  }
+
+  static constexpr int SizeInBits() { return Parameters::kBits; }
+
+ private:
+  static ValueType ConvertToBinary(ValueType bcd_value) {
+    ValueType result = 0;
+    ValueType multiplier = 1;
+    for (int shift = 0; shift < Parameters::kBits; shift += 4) {
+      result += ((bcd_value >> shift) & 0xf) * multiplier;
+      multiplier *= 10;
+    }
+    return result;
+  }
+
+  static ValueType ConvertToBcd(ValueType value) {
+    ValueType bcd_value = 0;
+    for (int shift = 0; shift < Parameters::kBits; shift += 4) {
+      bcd_value |= (value % 10) << shift;
+      value /= 10;
+    }
+    return bcd_value;
+  }
+
+  static constexpr ValueType MaxValue() {
+    return MaxBcd<ValueType>(Parameters::kBits);
+  }
+
+  BitViewType buffer_;
+};
+
+// FloatView is the view for the Emboss Float type.
+template <class Parameters, class BitViewType>
+class FloatView final {
+  static_assert(Parameters::kBits == 32 || Parameters::kBits == 64,
+                "Only 32- and 64-bit floats are currently supported.");
+
+ public:
+  using ValueType = typename support::FloatType<Parameters::kBits>::Type;
+
+  template <typename... Args>
+  explicit FloatView(Args &&... args)
+      : buffer_{::std::forward<Args>(args)...} {}
+  FloatView() : buffer_() {}
+  FloatView(const FloatView &) = default;
+  FloatView(FloatView &&) = default;
+  FloatView &operator=(const FloatView &) = default;
+  FloatView &operator=(FloatView &&) = default;
+  ~FloatView() = default;
+
+  ValueType Read() const { return ConvertToFloat(buffer_.ReadUInt()); }
+  ValueType UncheckedRead() const {
+    return ConvertToFloat(buffer_.UncheckedReadUInt());
+  }
+  void Write(ValueType value) const { EMBOSS_CHECK(TryToWrite(value)); }
+  bool TryToWrite(ValueType value) const {
+    if (!CouldWriteValue(value)) return false;
+    if (!IsComplete()) return false;
+    buffer_.WriteUInt(ConvertToUInt(value));
+    return true;
+  }
+  static constexpr bool CouldWriteValue(ValueType value) { return true; }
+  void UncheckedWrite(ValueType value) const {
+    buffer_.UncheckedWriteUInt(ConvertToUInt(value));
+  }
+
+  template <typename OtherView>
+  void CopyFrom(const OtherView &other) const {
+    Write(other.Read());
+  }
+  template <typename OtherView>
+  void UncheckedCopyFrom(const OtherView &other) const {
+    UncheckedWrite(other.UncheckedRead());
+  }
+  template <typename OtherView>
+  bool TryToCopyFrom(const OtherView &other) const {
+    return other.Ok() && TryToWrite(other.Read());
+  }
+
+  // All bit patterns in the underlying buffer are valid, so Ok() is always
+  // true if IsComplete() is true.
+  bool Ok() const { return IsComplete(); }
+  template <class OtherBitViewType>
+  bool Equals(const FloatView<Parameters, OtherBitViewType> &other) const {
+    return Read() == other.Read();
+  }
+  template <class OtherBitViewType>
+  bool UncheckedEquals(
+      const FloatView<Parameters, OtherBitViewType> &other) const {
+    return UncheckedRead() == other.UncheckedRead();
+  }
+  bool IsComplete() const {
+    return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits;
+  }
+
+  template <class Stream>
+  bool UpdateFromTextStream(Stream *stream) const {
+    return support::ReadFloatFromTextStream(this, stream);
+  }
+
+  template <class Stream>
+  void WriteToTextStream(Stream *stream,
+                         ::emboss::TextOutputOptions options) const {
+    support::WriteFloatToTextStream(Read(), stream, options);
+  }
+
+  static constexpr int SizeInBits() { return Parameters::kBits; }
+
+ private:
+  using UIntType = typename support::FloatType<Parameters::kBits>::UIntType;
+  static ValueType ConvertToFloat(UIntType bits) {
+    // TODO(bolms): This method assumes a few things that are not always
+    // strictly true; e.g., that uint32_t and float have the same endianness.
+    ValueType result;
+    memcpy(static_cast<void *>(&result), static_cast<void *>(&bits),
+           sizeof result);
+    return result;
+  }
+
+  static UIntType ConvertToUInt(ValueType value) {
+    // TODO(bolms): This method assumes a few things that are not always
+    // strictly true; e.g., that uint32_t and float have the same endianness.
+    UIntType bits;
+    memcpy(static_cast<void *>(&bits), static_cast<void *>(&value),
+           sizeof bits);
+    return bits;
+  }
+
+  BitViewType buffer_;
+};
+
+}  // namespace prelude
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_PRELUDE_H_
diff --git a/public/emboss_prelude_test.cc b/public/emboss_prelude_test.cc
new file mode 100644
index 0000000..97658b1
--- /dev/null
+++ b/public/emboss_prelude_test.cc
@@ -0,0 +1,727 @@
+// 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.
+
+#include "public/emboss_prelude.h"
+
+#include <type_traits>
+
+#include "public/emboss_cpp_util.h"
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace prelude {
+namespace test {
+
+using ::emboss::support::OffsetBitBlock;
+using ::emboss::support::ReadWriteContiguousBuffer;
+
+template <size_t kBits>
+using BitBlockN = ::emboss::support::BitBlock<
+    ::emboss::support::LittleEndianByteOrderer<ReadWriteContiguousBuffer>,
+    kBits>;
+
+template <size_t kBits>
+using ViewParameters = ::emboss::support::FixedSizeViewParameters<
+    kBits, ::emboss::support::AllValuesAreOk>;
+
+TEST(FlagView, Methods) {
+  uint8_t byte = 0;
+  auto flag_view =
+      FlagView<ViewParameters<1>, OffsetBitBlock<BitBlockN<8>>>{BitBlockN<8>{
+          ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)};
+  EXPECT_FALSE(flag_view.Read());
+  byte = 0xfe;
+  EXPECT_FALSE(flag_view.Read());
+  byte = 0x01;
+  EXPECT_TRUE(flag_view.Read());
+  byte = 0xff;
+  EXPECT_TRUE(flag_view.Read());
+  EXPECT_TRUE(flag_view.CouldWriteValue(false));
+  EXPECT_TRUE(flag_view.CouldWriteValue(true));
+  flag_view.Write(false);
+  EXPECT_EQ(0xfe, byte);
+  byte = 0xaa;
+  flag_view.Write(true);
+  EXPECT_EQ(0xab, byte);
+}
+
+TEST(FlagView, TextDecode) {
+  uint8_t byte = 0;
+  const auto flag_view =
+      FlagView<ViewParameters<1>, OffsetBitBlock<BitBlockN<8>>>{BitBlockN<8>{
+          ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)};
+  EXPECT_FALSE(UpdateFromText(flag_view, ""));
+  EXPECT_FALSE(UpdateFromText(flag_view, "FALSE"));
+  EXPECT_FALSE(UpdateFromText(flag_view, "TRUE"));
+  EXPECT_FALSE(UpdateFromText(flag_view, "+true"));
+  EXPECT_TRUE(UpdateFromText(flag_view, "true"));
+  EXPECT_EQ(0x01, byte);
+  EXPECT_TRUE(UpdateFromText(flag_view, "false"));
+  EXPECT_EQ(0x00, byte);
+  EXPECT_TRUE(UpdateFromText(flag_view, " true"));
+  EXPECT_EQ(0x01, byte);
+  {
+    auto stream = support::TextStream{" false  xxx"};
+    EXPECT_TRUE(flag_view.UpdateFromTextStream(&stream));
+    EXPECT_EQ(0x00, byte);
+    ::std::string token;
+    EXPECT_TRUE(::emboss::support::ReadToken(&stream, &token));
+    EXPECT_EQ("xxx", token);
+  }
+}
+
+TEST(FlagView, TextEncode) {
+  uint8_t byte = 0;
+  const auto flag_view =
+      FlagView<ViewParameters<1>, OffsetBitBlock<BitBlockN<8>>>{BitBlockN<8>{
+          ReadWriteContiguousBuffer{&byte, 1}}.GetOffsetStorage<1, 0>(0, 1)};
+  EXPECT_EQ("false", WriteToString(flag_view));
+  byte = 1;
+  EXPECT_EQ("true", WriteToString(flag_view));
+}
+
+template <template <typename, typename> class ViewType, int kMaxBits>
+void CheckViewSizeInBits() {
+  const int size_in_bits =
+      ViewType<ViewParameters<kMaxBits>, BitBlockN<64>>::SizeInBits();
+  EXPECT_EQ(size_in_bits, kMaxBits);
+  return CheckViewSizeInBits<ViewType, kMaxBits - 1>();
+}
+
+template <>
+void CheckViewSizeInBits<UIntView, 0>() {
+  return;
+}
+
+template <>
+void CheckViewSizeInBits<IntView, 0>() {
+  return;
+}
+
+template <>
+void CheckViewSizeInBits<BcdView, 0>() {
+  return;
+}
+
+TEST(UIntView, SizeInBits) { CheckViewSizeInBits<UIntView, 64>(); }
+
+TEST(IntView, SizeInBits) { CheckViewSizeInBits<IntView, 64>(); }
+
+TEST(BcdView, SizeInBits) { CheckViewSizeInBits<BcdView, 64>(); }
+
+template <size_t kBits>
+using UIntViewN = UIntView<ViewParameters<kBits>, BitBlockN<kBits>>;
+
+TEST(UIntView, ValueType) {
+  using BitBlockType = BitBlockN<64>;
+  EXPECT_TRUE(
+      (::std::is_same<uint8_t, UIntView<ViewParameters<8>,
+                                        BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint8_t, UIntView<ViewParameters<6>,
+                                        BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint16_t, UIntView<ViewParameters<9>,
+                                         BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint16_t, UIntView<ViewParameters<16>,
+                                         BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint32_t, UIntView<ViewParameters<17>,
+                                         BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint32_t, UIntView<ViewParameters<32>,
+                                         BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint64_t, UIntView<ViewParameters<33>,
+                                         BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint64_t, UIntView<ViewParameters<64>,
+                                         BitBlockType>::ValueType>::value));
+}
+
+TEST(UIntView, CouldWriteValue) {
+  EXPECT_TRUE(UIntViewN<8>::CouldWriteValue(0xff));
+  EXPECT_TRUE(UIntViewN<8>::CouldWriteValue(0));
+  EXPECT_FALSE(UIntViewN<8>::CouldWriteValue(0x100));
+  EXPECT_FALSE(UIntViewN<8>::CouldWriteValue(-1));
+  EXPECT_TRUE(UIntViewN<16>::CouldWriteValue(0xffff));
+  EXPECT_TRUE(UIntViewN<16>::CouldWriteValue(0));
+  EXPECT_FALSE(UIntViewN<16>::CouldWriteValue(0x10000));
+  EXPECT_FALSE(UIntViewN<16>::CouldWriteValue(-1));
+  EXPECT_TRUE(UIntViewN<32>::CouldWriteValue(0xffffffffU));
+  EXPECT_TRUE(UIntViewN<32>::CouldWriteValue(0xffffffffL));
+  EXPECT_TRUE(UIntViewN<32>::CouldWriteValue(0));
+  EXPECT_FALSE(UIntViewN<32>::CouldWriteValue(0x100000000L));
+  EXPECT_FALSE(UIntViewN<32>::CouldWriteValue(-1));
+  EXPECT_TRUE(UIntViewN<48>::CouldWriteValue(0x0000ffffffffffffUL));
+  EXPECT_TRUE(UIntViewN<48>::CouldWriteValue(0x0000ffffffffffffL));
+  EXPECT_TRUE(UIntViewN<48>::CouldWriteValue(0));
+  EXPECT_FALSE(UIntViewN<48>::CouldWriteValue(0x1000000000000UL));
+  EXPECT_FALSE(UIntViewN<48>::CouldWriteValue(0x1000000000000L));
+  EXPECT_FALSE(UIntViewN<48>::CouldWriteValue(-1));
+  EXPECT_TRUE(UIntViewN<64>::CouldWriteValue(0xffffffffffffffffUL));
+  EXPECT_TRUE(UIntViewN<64>::CouldWriteValue(0));
+  EXPECT_FALSE(UIntViewN<64>::CouldWriteValue(-1));
+}
+
+TEST(UIntView, CouldWriteValueNarrowing) {
+  auto narrowing_could_write = [](int value) {
+    return UIntViewN<8>::CouldWriteValue(value);
+  };
+  EXPECT_TRUE(narrowing_could_write(0));
+  EXPECT_TRUE(narrowing_could_write(255));
+  EXPECT_FALSE(narrowing_could_write(-1));
+  EXPECT_FALSE(narrowing_could_write(256));
+}
+
+TEST(UIntView, ReadAndWriteWithSufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}};
+  auto uint64_view =
+      UIntViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}};
+  EXPECT_EQ(0x090a0b0c0d0e0f10UL, uint64_view.Read());
+  EXPECT_EQ(0x090a0b0c0d0e0f10UL, uint64_view.UncheckedRead());
+  uint64_view.Write(0x100f0e0d0c0b0a09UL);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}),
+            bytes);
+  uint64_view.UncheckedWrite(0x090a0b0c0d0e0f10UL);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}),
+            bytes);
+  EXPECT_TRUE(uint64_view.TryToWrite(0x100f0e0d0c0b0a09UL));
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}),
+            bytes);
+  EXPECT_TRUE(uint64_view.TryToWrite(0x090a0b0c0d0e0f10UL));
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}),
+            bytes);
+  EXPECT_TRUE(uint64_view.Ok());
+  EXPECT_TRUE(uint64_view.IsComplete());
+}
+
+TEST(UIntView, ReadAndWriteWithInsufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}};
+  auto uint64_view =
+      UIntViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 4}}};
+  EXPECT_DEATH(uint64_view.Read(), "");
+  EXPECT_EQ(0x090a0b0c0d0e0f10UL, uint64_view.UncheckedRead());
+  EXPECT_DEATH(uint64_view.Write(0x100f0e0d0c0b0a09UL), "");
+  EXPECT_FALSE(uint64_view.TryToWrite(0x100f0e0d0c0b0a09UL));
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}),
+            bytes);
+  uint64_view.UncheckedWrite(0x100f0e0d0c0b0a09UL);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}),
+            bytes);
+  EXPECT_FALSE(uint64_view.Ok());
+  EXPECT_FALSE(uint64_view.IsComplete());
+  uint64_view.UncheckedWrite(0x090a0b0c0d0e0f10UL);
+}
+
+TEST(UIntView, NonPowerOfTwoSize) {
+  ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}};
+  auto uint24_view =
+      UIntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}};
+  EXPECT_EQ(0x0e0f10, uint24_view.Read());
+  EXPECT_EQ(0x0e0f10, uint24_view.UncheckedRead());
+  EXPECT_DEATH(uint24_view.Write(0x1000000), "");
+  uint24_view.Write(0x100f0e);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0x10, 0x0d}}), bytes);
+  uint24_view.UncheckedWrite(0x1000000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x0d}}), bytes);
+  EXPECT_TRUE(uint24_view.Ok());
+  EXPECT_TRUE(uint24_view.IsComplete());
+}
+
+TEST(UIntView, NonPowerOfTwoSizeInsufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}};
+  auto uint24_view =
+      UIntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 2}}};
+  EXPECT_DEATH(uint24_view.Read(), "");
+  EXPECT_EQ(0x0e0f10, uint24_view.UncheckedRead());
+  EXPECT_DEATH(uint24_view.Write(0x100f0e), "");
+  uint24_view.UncheckedWrite(0x100f0e);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0x10, 0x0d}}), bytes);
+  uint24_view.UncheckedWrite(0x1000000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x0d}}), bytes);
+  EXPECT_FALSE(uint24_view.Ok());
+  EXPECT_FALSE(uint24_view.IsComplete());
+}
+
+TEST(UIntView, NonByteSize) {
+  ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x80, 0x80}};
+  auto uint23_view =
+      UIntView<ViewParameters<23>, OffsetBitBlock<BitBlockN<24>>>{BitBlockN<24>{
+          ReadWriteContiguousBuffer{bytes.data(),
+                                    3}}.GetOffsetStorage<1, 0>(0, 23)};
+  EXPECT_EQ(0x0, uint23_view.Read());
+  EXPECT_FALSE(uint23_view.CouldWriteValue(0x800f0e));
+  EXPECT_FALSE(uint23_view.CouldWriteValue(0x800000));
+  EXPECT_TRUE(uint23_view.CouldWriteValue(0x7fffff));
+  EXPECT_DEATH(uint23_view.Write(0x800f0e), "");
+  uint23_view.Write(0x400f0e);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0xc0, 0x80}}), bytes);
+  uint23_view.UncheckedWrite(0x1000000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x80, 0x80}}), bytes);
+  EXPECT_TRUE(uint23_view.Ok());
+  EXPECT_TRUE(uint23_view.IsComplete());
+}
+
+TEST(UIntView, TextDecode) {
+  ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x00, 0xff}};
+  const auto uint24_view =
+      UIntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}};
+  EXPECT_TRUE(UpdateFromText(uint24_view, "23"));
+  EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes);
+  EXPECT_EQ(23, uint24_view.Read());
+  EXPECT_FALSE(UpdateFromText(uint24_view, "16777216"));
+  EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes);
+  EXPECT_TRUE(UpdateFromText(uint24_view, "16777215"));
+  EXPECT_EQ((::std::vector<uint8_t>{{0xff, 0xff, 0xff, 0xff}}), bytes);
+  EXPECT_TRUE(UpdateFromText(uint24_view, "0x01_0203"));
+  EXPECT_EQ((::std::vector<uint8_t>{{0x03, 0x02, 0x01, 0xff}}), bytes);
+}
+
+template <size_t kBits>
+using IntViewN = IntView<ViewParameters<kBits>, BitBlockN<kBits>>;
+
+TEST(IntView, ValueType) {
+  using BitBlockType = BitBlockN<64>;
+  EXPECT_TRUE(
+      (::std::is_same<
+          int8_t, IntView<ViewParameters<8>, BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<
+          int8_t, IntView<ViewParameters<6>, BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<int16_t, IntView<ViewParameters<9>,
+                                       BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<int16_t, IntView<ViewParameters<16>,
+                                       BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<int32_t, IntView<ViewParameters<17>,
+                                       BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<int32_t, IntView<ViewParameters<32>,
+                                       BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<int64_t, IntView<ViewParameters<33>,
+                                       BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<int64_t, IntView<ViewParameters<64>,
+                                       BitBlockType>::ValueType>::value));
+}
+
+TEST(IntView, CouldWriteValue) {
+  // Note that many values are in decimal in order to avoid C++'s implicit
+  // conversions to unsigned for hex constants.
+  EXPECT_TRUE(IntViewN<8>::CouldWriteValue(0x7f));
+  EXPECT_TRUE(IntViewN<8>::CouldWriteValue(-0x80));
+  EXPECT_FALSE(IntViewN<8>::CouldWriteValue(0x80));
+  EXPECT_FALSE(IntViewN<8>::CouldWriteValue(0x8000000000000000UL));
+  EXPECT_FALSE(IntViewN<8>::CouldWriteValue(-0x81));
+  EXPECT_TRUE(IntViewN<16>::CouldWriteValue(32767));
+  EXPECT_TRUE(IntViewN<16>::CouldWriteValue(0));
+  EXPECT_FALSE(IntViewN<16>::CouldWriteValue(0x8000));
+  EXPECT_FALSE(IntViewN<16>::CouldWriteValue(-0x8001));
+  EXPECT_TRUE(IntViewN<32>::CouldWriteValue(0x7fffffffU));
+  EXPECT_TRUE(IntViewN<32>::CouldWriteValue(0x7fffffffL));
+  EXPECT_FALSE(IntViewN<32>::CouldWriteValue(0x80000000U));
+  EXPECT_FALSE(IntViewN<32>::CouldWriteValue(-2147483649L));
+  EXPECT_TRUE(IntViewN<48>::CouldWriteValue(0x00007fffffffffffUL));
+  EXPECT_FALSE(IntViewN<48>::CouldWriteValue(140737488355328L));
+  EXPECT_FALSE(IntViewN<48>::CouldWriteValue(-140737488355329L));
+  EXPECT_TRUE(IntViewN<64>::CouldWriteValue(0x7fffffffffffffffUL));
+  EXPECT_TRUE(IntViewN<64>::CouldWriteValue(9223372036854775807L));
+  EXPECT_TRUE(IntViewN<64>::CouldWriteValue(-9223372036854775807L - 1));
+  EXPECT_FALSE(IntViewN<64>::CouldWriteValue(0x8000000000000000UL));
+}
+
+TEST(IntView, CouldWriteValueNarrowing) {
+  auto narrowing_could_write = [](int value) {
+    return IntViewN<8>::CouldWriteValue(value);
+  };
+  EXPECT_TRUE(narrowing_could_write(-128));
+  EXPECT_TRUE(narrowing_could_write(127));
+  EXPECT_FALSE(narrowing_could_write(-129));
+  EXPECT_FALSE(narrowing_could_write(128));
+}
+
+TEST(IntView, ReadAndWriteWithSufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}};
+  auto int64_view =
+      IntViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}};
+  EXPECT_EQ(0x090a0b0c0d0e0f10L, int64_view.Read());
+  EXPECT_EQ(0x090a0b0c0d0e0f10L, int64_view.UncheckedRead());
+  int64_view.Write(0x100f0e0d0c0b0a09L);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}),
+            bytes);
+  int64_view.UncheckedWrite(0x090a0b0c0d0e0f10L);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}),
+            bytes);
+  EXPECT_TRUE(int64_view.TryToWrite(0x100f0e0d0c0b0a09L));
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}),
+            bytes);
+  int64_view.Write(-0x100f0e0d0c0b0a09L);
+  EXPECT_EQ(-0x100f0e0d0c0b0a09L, int64_view.Read());
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0xf7, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, 0xef, 0x08}}),
+            bytes);
+  EXPECT_TRUE(int64_view.Ok());
+  EXPECT_TRUE(int64_view.IsComplete());
+}
+
+TEST(IntView, ReadAndWriteWithInsufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {
+      {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}};
+  auto int64_view =
+      IntViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 4}}};
+  EXPECT_DEATH(int64_view.Read(), "");
+  EXPECT_EQ(0x090a0b0c0d0e0f10L, int64_view.UncheckedRead());
+  EXPECT_DEATH(int64_view.Write(0x100f0e0d0c0b0a09L), "");
+  EXPECT_FALSE(int64_view.TryToWrite(0x100f0e0d0c0b0a09L));
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08}}),
+            bytes);
+  int64_view.UncheckedWrite(0x100f0e0d0c0b0a09L);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x08}}),
+            bytes);
+  EXPECT_FALSE(int64_view.Ok());
+  EXPECT_FALSE(int64_view.IsComplete());
+}
+
+TEST(IntView, NonPowerOfTwoSize) {
+  ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}};
+  auto int24_view =
+      IntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}};
+  EXPECT_EQ(0x0e0f10, int24_view.Read());
+  EXPECT_EQ(0x0e0f10, int24_view.UncheckedRead());
+  EXPECT_DEATH(int24_view.Write(0x1000000), "");
+  int24_view.Write(0x100f0e);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0x10, 0x0d}}), bytes);
+  int24_view.Write(-0x100f0e);
+  EXPECT_EQ((::std::vector<uint8_t>{{0xf2, 0xf0, 0xef, 0x0d}}), bytes);
+  EXPECT_DEATH(int24_view.Write(0x1000000), "");
+  int24_view.UncheckedWrite(0x1000000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x0d}}), bytes);
+  EXPECT_TRUE(int24_view.Ok());
+  EXPECT_TRUE(int24_view.IsComplete());
+}
+
+TEST(IntView, NonPowerOfTwoSizeInsufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {{0x10, 0x0f, 0x0e, 0x0d}};
+  auto int24_view =
+      IntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 2}}};
+  EXPECT_DEATH(int24_view.Read(), "");
+  EXPECT_EQ(0x0e0f10, int24_view.UncheckedRead());
+  EXPECT_DEATH(int24_view.Write(0x100f0e), "");
+  int24_view.UncheckedWrite(0x100f0e);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0x10, 0x0d}}), bytes);
+  int24_view.UncheckedWrite(0x1000000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x0d}}), bytes);
+  EXPECT_FALSE(int24_view.Ok());
+  EXPECT_FALSE(int24_view.IsComplete());
+}
+
+TEST(IntView, NonByteSize) {
+  ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x80, 0x80}};
+  auto int23_view =
+      IntView<ViewParameters<23>, OffsetBitBlock<BitBlockN<24>>>{BitBlockN<24>{
+          ReadWriteContiguousBuffer{bytes.data(),
+                                    3}}.GetOffsetStorage<1, 0>(0, 23)};
+  EXPECT_EQ(0x0, int23_view.Read());
+  EXPECT_FALSE(int23_view.CouldWriteValue(0x400f0e));
+  EXPECT_DEATH(int23_view.Write(0x400f0e), "");
+  int23_view.Write(0x200f0e);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x0e, 0x0f, 0xa0, 0x80}}), bytes);
+  int23_view.Write(-0x400000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0xc0, 0x80}}), bytes);
+  int23_view.UncheckedWrite(0x1000000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x80, 0x80}}), bytes);
+  EXPECT_TRUE(int23_view.Ok());
+  EXPECT_TRUE(int23_view.IsComplete());
+}
+
+TEST(IntView, OneBit) {
+  uint8_t bytes[] = {0xfe};
+  auto int1_view =
+      IntView<ViewParameters<1>, OffsetBitBlock<BitBlockN<8>>>{BitBlockN<8>{
+          ReadWriteContiguousBuffer{bytes, 1}}.GetOffsetStorage<1, 0>(0, 1)};
+  EXPECT_TRUE(int1_view.Ok());
+  EXPECT_TRUE(int1_view.IsComplete());
+  EXPECT_EQ(0, int1_view.Read());
+  EXPECT_FALSE(int1_view.CouldWriteValue(1));
+  EXPECT_TRUE(int1_view.CouldWriteValue(0));
+  EXPECT_TRUE(int1_view.CouldWriteValue(-1));
+  EXPECT_DEATH(int1_view.Write(1), "");
+  int1_view.Write(-1);
+  EXPECT_EQ(0xff, bytes[0]);
+  EXPECT_EQ(-1, int1_view.Read());
+  int1_view.Write(0);
+  EXPECT_EQ(0xfe, bytes[0]);
+  bytes[0] = 0;
+  int1_view.Write(-1);
+  EXPECT_EQ(0x01, bytes[0]);
+}
+
+TEST(IntView, TextDecode) {
+  ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x00, 0xff}};
+  const auto int24_view =
+      IntViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}};
+  EXPECT_TRUE(UpdateFromText(int24_view, "23"));
+  EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes);
+  EXPECT_EQ(23, int24_view.Read());
+  EXPECT_FALSE(UpdateFromText(int24_view, "16777216"));
+  EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes);
+  EXPECT_FALSE(UpdateFromText(int24_view, "16777215"));
+  EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes);
+  EXPECT_FALSE(UpdateFromText(int24_view, "8388608"));
+  EXPECT_EQ((::std::vector<uint8_t>{{23, 0x00, 0x00, 0xff}}), bytes);
+  EXPECT_TRUE(UpdateFromText(int24_view, "8388607"));
+  EXPECT_EQ((::std::vector<uint8_t>{{0xff, 0xff, 0x7f, 0xff}}), bytes);
+  EXPECT_TRUE(UpdateFromText(int24_view, "-8388608"));
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x80, 0xff}}), bytes);
+  EXPECT_TRUE(UpdateFromText(int24_view, "-1"));
+  EXPECT_EQ((::std::vector<uint8_t>{{0xff, 0xff, 0xff, 0xff}}), bytes);
+  EXPECT_TRUE(UpdateFromText(int24_view, "0x01_0203"));
+  EXPECT_EQ((::std::vector<uint8_t>{{0x03, 0x02, 0x01, 0xff}}), bytes);
+  EXPECT_TRUE(UpdateFromText(int24_view, "-0x01_0203"));
+  EXPECT_EQ((::std::vector<uint8_t>{{0xfd, 0xfd, 0xfe, 0xff}}), bytes);
+  EXPECT_FALSE(UpdateFromText(int24_view, "- 0x01_0203"));
+  EXPECT_EQ((::std::vector<uint8_t>{{0xfd, 0xfd, 0xfe, 0xff}}), bytes);
+}
+
+TEST(MaxBcd, Values) {
+  EXPECT_EQ(0, MaxBcd<uint64_t>(0));
+  EXPECT_EQ(1, MaxBcd<uint64_t>(1));
+  EXPECT_EQ(3, MaxBcd<uint64_t>(2));
+  EXPECT_EQ(7, MaxBcd<uint64_t>(3));
+  EXPECT_EQ(9, MaxBcd<uint64_t>(4));
+  EXPECT_EQ(19, MaxBcd<uint64_t>(5));
+  EXPECT_EQ(39, MaxBcd<uint64_t>(6));
+  EXPECT_EQ(79, MaxBcd<uint64_t>(7));
+  EXPECT_EQ(99, MaxBcd<uint64_t>(8));
+  EXPECT_EQ(199, MaxBcd<uint64_t>(9));
+  EXPECT_EQ(999, MaxBcd<uint64_t>(12));
+  EXPECT_EQ(9999, MaxBcd<uint64_t>(16));
+  EXPECT_EQ(999999, MaxBcd<uint64_t>(24));
+  EXPECT_EQ(3999999999999999UL, MaxBcd<uint64_t>(62));
+  EXPECT_EQ(7999999999999999UL, MaxBcd<uint64_t>(63));
+  EXPECT_EQ(9999999999999999UL, MaxBcd<uint64_t>(64));
+  // Max uint64_t is 18446744073709551616, which is big enough to hold a 76-bit
+  // BCD value.
+  EXPECT_EQ(19999999999999999UL, MaxBcd<uint64_t>(65));
+  EXPECT_EQ(39999999999999999UL, MaxBcd<uint64_t>(66));
+  EXPECT_EQ(99999999999999999UL, MaxBcd<uint64_t>(68));
+  EXPECT_EQ(999999999999999999UL, MaxBcd<uint64_t>(72));
+  EXPECT_EQ(9999999999999999999UL, MaxBcd<uint64_t>(76));
+}
+
+TEST(IsBcd, Values) {
+  EXPECT_TRUE(IsBcd(0x00U));
+  EXPECT_TRUE(IsBcd(0x12U));
+  EXPECT_TRUE(IsBcd(0x91U));
+  EXPECT_TRUE(IsBcd(0x99U));
+  EXPECT_TRUE(IsBcd(uint8_t{0x00}));
+  EXPECT_TRUE(IsBcd(uint8_t{0x99}));
+  EXPECT_TRUE(IsBcd(uint16_t{0x0000}));
+  EXPECT_TRUE(IsBcd(uint16_t{0x9999}));
+  EXPECT_TRUE(IsBcd(0x9999999999999999UL));
+  EXPECT_FALSE(IsBcd(uint8_t{0x0a}));
+  EXPECT_FALSE(IsBcd(uint8_t{0xa0}));
+  EXPECT_FALSE(IsBcd(uint8_t{0xff}));
+  EXPECT_FALSE(IsBcd(uint16_t{0x0a00}));
+  EXPECT_FALSE(IsBcd(uint16_t{0x000a}));
+  EXPECT_FALSE(IsBcd(0x999999999999999aUL));
+  EXPECT_FALSE(IsBcd(0xaUL));
+  EXPECT_FALSE(IsBcd(0xa000000000000000UL));
+  EXPECT_FALSE(IsBcd(0xf000000000000000UL));
+  EXPECT_FALSE(IsBcd(0xffffffffffffffffUL));
+}
+
+TEST(BcdView, ValueType) {
+  using BitBlockType = BitBlockN<64>;
+  EXPECT_TRUE(
+      (::std::is_same<uint8_t, BcdView<ViewParameters<8>,
+                                       BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint8_t, BcdView<ViewParameters<6>,
+                                       BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint16_t, BcdView<ViewParameters<9>,
+                                        BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint16_t, BcdView<ViewParameters<16>,
+                                        BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint32_t, BcdView<ViewParameters<17>,
+                                        BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint32_t, BcdView<ViewParameters<32>,
+                                        BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint64_t, BcdView<ViewParameters<33>,
+                                        BitBlockType>::ValueType>::value));
+  EXPECT_TRUE(
+      (::std::is_same<uint64_t, BcdView<ViewParameters<64>,
+                                        BitBlockType>::ValueType>::value));
+}
+
+TEST(BcdView, CouldWriteValue) {
+  EXPECT_TRUE((BcdView<ViewParameters<64>, int>::CouldWriteValue(0)));
+  EXPECT_TRUE(
+      (BcdView<ViewParameters<64>, int>::CouldWriteValue(9999999999999999)));
+  EXPECT_FALSE(
+      (BcdView<ViewParameters<64>, int>::CouldWriteValue(10000000000000000)));
+  EXPECT_FALSE((
+      BcdView<ViewParameters<64>, int>::CouldWriteValue(0xffffffffffffffffUL)));
+  EXPECT_FALSE(
+      (BcdView<ViewParameters<48>, int>::CouldWriteValue(9999999999999999)));
+  EXPECT_TRUE(
+      (BcdView<ViewParameters<48>, int>::CouldWriteValue(999999999999)));
+  EXPECT_TRUE((BcdView<ViewParameters<48>, int>::CouldWriteValue(0)));
+  EXPECT_FALSE((BcdView<ViewParameters<48>, int>::CouldWriteValue(
+      (0xffUL << 48) + 999999999999)));
+  EXPECT_FALSE(
+      (BcdView<ViewParameters<48>, int>::CouldWriteValue(10000000000000000)));
+  EXPECT_FALSE((
+      BcdView<ViewParameters<48>, int>::CouldWriteValue(0xffffffffffffffffUL)));
+}
+
+template <size_t kBits>
+using BcdViewN = BcdView<ViewParameters<kBits>, BitBlockN<kBits>>;
+
+TEST(BcdView, ReadAndWriteWithSufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {
+      {0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08}};
+  auto bcd64_view =
+      BcdViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 8}}};
+  EXPECT_EQ(910111213141516UL, bcd64_view.Read());
+  EXPECT_EQ(910111213141516UL, bcd64_view.UncheckedRead());
+  bcd64_view.Write(1615141312111009);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08}}),
+            bytes);
+  bcd64_view.UncheckedWrite(910111213141516UL);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08}}),
+            bytes);
+  EXPECT_TRUE(bcd64_view.TryToWrite(1615141312111009));
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08}}),
+            bytes);
+  EXPECT_TRUE(bcd64_view.Ok());
+  EXPECT_TRUE(bcd64_view.IsComplete());
+}
+
+TEST(BcdView, ReadAndWriteWithInsufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {
+      {0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08}};
+  auto bcd64_view =
+      BcdViewN<64>{BitBlockN<64>{ReadWriteContiguousBuffer{bytes.data(), 4}}};
+  EXPECT_DEATH(bcd64_view.Read(), "");
+  EXPECT_EQ(910111213141516UL, bcd64_view.UncheckedRead());
+  EXPECT_DEATH(bcd64_view.Write(1615141312111009), "");
+  EXPECT_FALSE(bcd64_view.TryToWrite(1615141312111009));
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08}}),
+            bytes);
+  bcd64_view.UncheckedWrite(1615141312111009);
+  EXPECT_EQ((::std::vector<uint8_t>{
+                {0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08}}),
+            bytes);
+  EXPECT_FALSE(bcd64_view.Ok());
+  EXPECT_FALSE(bcd64_view.IsComplete());
+}
+
+TEST(BcdView, NonPowerOfTwoSize) {
+  ::std::vector<uint8_t> bytes = {{0x16, 0x15, 0x14, 0x13}};
+  auto bcd24_view =
+      BcdViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 3}}};
+  EXPECT_EQ(141516, bcd24_view.Read());
+  EXPECT_EQ(141516, bcd24_view.UncheckedRead());
+  bcd24_view.Write(161514);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x14, 0x15, 0x16, 0x13}}), bytes);
+  EXPECT_DEATH(bcd24_view.Write(1000000), "");
+  bcd24_view.UncheckedWrite(1000000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x13}}), bytes);
+  bcd24_view.UncheckedWrite(141516);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x16, 0x15, 0x14, 0x13}}), bytes);
+  EXPECT_TRUE(bcd24_view.Ok());
+  EXPECT_TRUE(bcd24_view.IsComplete());
+}
+
+TEST(BcdView, NonPowerOfTwoSizeInsufficientBuffer) {
+  ::std::vector<uint8_t> bytes = {{0x16, 0x15, 0x14, 0x13}};
+  auto bcd24_view =
+      BcdViewN<24>{BitBlockN<24>{ReadWriteContiguousBuffer{bytes.data(), 2}}};
+  EXPECT_DEATH(bcd24_view.Read(), "");
+  EXPECT_EQ(141516, bcd24_view.UncheckedRead());
+  EXPECT_DEATH(bcd24_view.Write(161514), "");
+  bcd24_view.UncheckedWrite(161514);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x14, 0x15, 0x16, 0x13}}), bytes);
+  bcd24_view.UncheckedWrite(1000000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x00, 0x13}}), bytes);
+  EXPECT_FALSE(bcd24_view.Ok());
+  EXPECT_FALSE(bcd24_view.IsComplete());
+}
+
+TEST(BcdView, NonByteSize) {
+  ::std::vector<uint8_t> bytes = {{0x00, 0x00, 0x80, 0x80}};
+  auto bcd23_view =
+      BcdView<ViewParameters<23>, OffsetBitBlock<BitBlockN<24>>>{BitBlockN<24>{
+          ReadWriteContiguousBuffer{bytes.data(),
+                                    3}}.GetOffsetStorage<1, 0>(0, 23)};
+  EXPECT_EQ(0x0, bcd23_view.Read());
+  EXPECT_FALSE(bcd23_view.CouldWriteValue(800000));
+  EXPECT_TRUE(bcd23_view.CouldWriteValue(799999));
+  EXPECT_DEATH(bcd23_view.Write(800000), "");
+  bcd23_view.Write(432198);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x98, 0x21, 0xc3, 0x80}}), bytes);
+  bcd23_view.UncheckedWrite(800000);
+  EXPECT_EQ((::std::vector<uint8_t>{{0x00, 0x00, 0x80, 0x80}}), bytes);
+  EXPECT_TRUE(bcd23_view.Ok());
+  EXPECT_TRUE(bcd23_view.IsComplete());
+}
+
+TEST(BcdLittleEndianView, AllByteValues) {
+  uint8_t byte = 0;
+  auto bcd8_view =
+      BcdViewN<8>{BitBlockN<8>{ReadWriteContiguousBuffer{&byte, 1}}};
+  for (int i = 0; i < 15; ++i) {
+    for (int j = 0; j < 15; ++j) {
+      byte = i * 16 + j;
+      if (i > 9 || j > 9) {
+        EXPECT_FALSE(bcd8_view.Ok()) << i << ", " << j;
+      } else {
+        EXPECT_TRUE(bcd8_view.Ok()) << i << ", " << j;
+      }
+    }
+  }
+}
+
+}  // namespace test
+}  // namespace prelude
+}  // namespace emboss
diff --git a/public/emboss_test_util.h b/public/emboss_test_util.h
new file mode 100644
index 0000000..89f9855
--- /dev/null
+++ b/public/emboss_test_util.h
@@ -0,0 +1,98 @@
+// 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.
+
+#ifndef EMBOSS_PUBLIC_EMBOSS_TEST_UTIL_H_
+#define EMBOSS_PUBLIC_EMBOSS_TEST_UTIL_H_
+
+#include <cctype>
+#include <iterator>
+#include <ostream>
+#include <string>
+
+#include "public/emboss_text_util.h"
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "third_party/absl/memory/memory.h"
+#include "third_party/googletest/googletest/include/gtest/internal/gtest-internal.h"
+
+namespace emboss {
+
+class EmbMatcher {
+ public:
+  template <typename ViewType>
+  explicit EmbMatcher(ViewType compare_to)
+      : compare_to_ok_(compare_to.Ok()),
+        compare_to_lines_(SplitToLines(
+            compare_to_ok_ ? WriteToString(compare_to, MultilineText()) : "")) {
+  }
+
+  template <typename ViewType>
+  bool MatchAndExplain(ViewType compare_from,
+                       ::testing::MatchResultListener* listener) const {
+    if (!compare_to_ok_) {
+      *listener << "View for comparison to is not OK.";
+      return false;
+    }
+
+    if (!compare_from.Ok()) {
+      *listener << "View for comparison from is not OK.";
+      return false;
+    }
+
+    const auto compare_from_lines =
+        SplitToLines(WriteToString(compare_from, MultilineText()));
+    if (compare_from_lines != compare_to_lines_) {
+      *listener << "\n"
+                << ::testing::internal::edit_distance::CreateUnifiedDiff(
+                       compare_to_lines_, compare_from_lines);
+      return false;
+    }
+
+    return true;
+  }
+
+  // Describes the property of a value matching this matcher.
+  void DescribeTo(::std::ostream* os) const { *os << "are equal"; }
+
+  // Describes the property of a value NOT matching this matcher.
+  void DescribeNegationTo(::std::ostream* os) const { *os << "are NOT equal"; }
+
+ private:
+  // Splits the given string on '\n' boundaries and returns a vector of those
+  // strings.
+  ::std::vector<::std::string> SplitToLines(const ::std::string& input) const {
+    constexpr char kNewLine = '\n';
+
+    ::std::stringstream ss(input);
+    ss.ignore(::std::numeric_limits<::std::streamsize>::max(), kNewLine);
+
+    ::std::vector<::std::string> lines;
+    for (::std::string line; ::std::getline(ss, line, kNewLine);) {
+      lines.push_back(::std::move(line));
+    }
+    return lines;
+  }
+
+  const bool compare_to_ok_;
+  const ::std::vector<::std::string> compare_to_lines_;
+};
+
+template <typename ViewType>
+::testing::PolymorphicMatcher<EmbMatcher> EqualsEmb(ViewType view) {
+  return ::testing::MakePolymorphicMatcher(EmbMatcher(view));
+}
+
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_TEST_UTIL_H_
diff --git a/public/emboss_test_util_test.cc b/public/emboss_test_util_test.cc
new file mode 100644
index 0000000..71a34fe
--- /dev/null
+++ b/public/emboss_test_util_test.cc
@@ -0,0 +1,134 @@
+// 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.
+
+#include "public/emboss_test_util.h"
+
+#include "testdata/complex_structure.emb.h"
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace test {
+namespace {
+
+class EmbossTestUtilTest : public ::testing::Test {
+ protected:
+  EmbossTestUtilTest() { b_.s().Write(1); }
+  std::array<uint8, 64> buf_a_{};
+  ::emboss_test::ComplexWriter a_{&buf_a_};
+  std::array<char, 64> buf_b_{};
+  ::emboss_test::ComplexWriter b_{&buf_b_};
+};
+
+TEST_F(EmbossTestUtilTest, EqualsEmb) {
+  EXPECT_THAT(a_, EqualsEmb(a_));
+  EXPECT_THAT(b_, EqualsEmb(b_));
+
+  EXPECT_THAT(a_, ::testing::Not(EqualsEmb(b_)));
+  EXPECT_THAT(b_, ::testing::Not(EqualsEmb(a_)));
+}
+
+TEST_F(EmbossTestUtilTest, NotOkView) {
+  auto null_view = ::emboss_test::ComplexView(nullptr);
+  EXPECT_THAT(a_, ::testing::Not(EqualsEmb(null_view)));
+  EXPECT_THAT(b_, ::testing::Not(EqualsEmb(null_view)));
+  EXPECT_THAT(null_view, ::testing::Not(EqualsEmb(null_view)));
+  EXPECT_THAT(null_view, ::testing::Not(EqualsEmb(a_)));
+  EXPECT_THAT(null_view, ::testing::Not(EqualsEmb(b_)));
+}
+
+TEST_F(EmbossTestUtilTest, NotOkViewMatcherDescribe) {
+  auto null_view = ::emboss_test::ComplexView(nullptr);
+
+  ::testing::StringMatchResultListener listener;
+  EqualsEmb(a_).impl().MatchAndExplain(null_view, &listener);
+  EXPECT_EQ(listener.str(), "View for comparison from is not OK.");
+
+  listener.Clear();
+  EqualsEmb(null_view).impl().MatchAndExplain(a_, &listener);
+  EXPECT_EQ(listener.str(), "View for comparison to is not OK.");
+}
+
+TEST_F(EmbossTestUtilTest, MatcherDescribeEquivalent) {
+  ::std::stringstream ss;
+  EqualsEmb(a_).impl().DescribeTo(&ss);
+  EXPECT_EQ(ss.str(), "are equal");
+}
+
+TEST_F(EmbossTestUtilTest, MatcherDescribeNotEquivalent) {
+  ::std::stringstream ss;
+  EqualsEmb(a_).impl().DescribeNegationTo(&ss);
+  EXPECT_EQ(ss.str(), "are NOT equal");
+}
+
+TEST_F(EmbossTestUtilTest, MatcherExplainEquivalent) {
+  ::testing::StringMatchResultListener listener;
+
+  EqualsEmb(a_).impl().MatchAndExplain(a_, &listener);
+  EXPECT_EQ(listener.str(), "");
+
+  EqualsEmb(b_).impl().MatchAndExplain(b_, &listener);
+  EXPECT_EQ(listener.str(), "");
+}
+
+TEST_F(EmbossTestUtilTest, MatcherExplainNotEquivalent) {
+  ::testing::StringMatchResultListener listener;
+  EqualsEmb(a_).impl().MatchAndExplain(b_, &listener);
+  EXPECT_EQ(listener.str(), R"(
+@@ -1,3 +1,3 @@
+-  s: 0  # 0x0
++  s: 1  # 0x1
+   u: 0  # 0x0
+   i: 0  # 0x0
+@@ +4,34 @@
+   b: 0  # 0x0
+   a: {
++    [0]: {
++      [0]: {
++        a: {
++          x: 0  # 0x0
++          l: 0  # 0x0
++          h: 0  # 0x0
++        }
++      }
++      [1]: {
++        a: {
++          x: 0  # 0x0
++          l: 0  # 0x0
++          h: 0  # 0x0
++        }
++      }
++      [2]: {
++        a: {
++          x: 0  # 0x0
++          l: 0  # 0x0
++          h: 0  # 0x0
++        }
++      }
++      [3]: {
++        a: {
++          x: 0  # 0x0
++          l: 0  # 0x0
++          h: 0  # 0x0
++        }
++      }
++    }
+   }
+   a0: 0  # 0x0
+)");
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace emboss
diff --git a/public/emboss_text_util.h b/public/emboss_text_util.h
new file mode 100644
index 0000000..6ed1e05
--- /dev/null
+++ b/public/emboss_text_util.h
@@ -0,0 +1,798 @@
+// 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.
+
+// This header contains functionality related to Emboss text output.
+#ifndef EMBOSS_PUBLIC_EMBOSS_TEXT_UTIL_H_
+#define EMBOSS_PUBLIC_EMBOSS_TEXT_UTIL_H_
+
+#include <array>
+#include <climits>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "public/emboss_defines.h"
+
+namespace emboss {
+
+// TextOutputOptions are used to configure text output.  Typically, one can just
+// use a default TextOutputOptions() (for compact output) or MultilineText()
+// (for reasonable formatted output).
+class TextOutputOptions final {
+ public:
+  TextOutputOptions() = default;
+
+  TextOutputOptions PlusOneIndent() const {
+    TextOutputOptions result = *this;
+    result.current_indent_ += indent();
+    return result;
+  }
+
+  TextOutputOptions Multiline(bool new_value) const {
+    TextOutputOptions result = *this;
+    result.multiline_ = new_value;
+    return result;
+  }
+
+  TextOutputOptions WithIndent(::std::string new_value) const {
+    TextOutputOptions result = *this;
+    result.indent_ = ::std::move(new_value);
+    return result;
+  }
+
+  TextOutputOptions WithComments(bool new_value) const {
+    TextOutputOptions result = *this;
+    result.comments_ = new_value;
+    return result;
+  }
+
+  TextOutputOptions WithDigitGrouping(bool new_value) const {
+    TextOutputOptions result = *this;
+    result.digit_grouping_ = new_value;
+    return result;
+  }
+
+  TextOutputOptions WithNumericBase(int new_value) const {
+    TextOutputOptions result = *this;
+    result.numeric_base_ = new_value;
+    return result;
+  }
+
+  ::std::string current_indent() const { return current_indent_; }
+  ::std::string indent() const { return indent_; }
+  bool multiline() const { return multiline_; }
+  bool digit_grouping() const { return digit_grouping_; }
+  bool comments() const { return comments_; }
+  uint8_t numeric_base() const { return numeric_base_; }
+
+ private:
+  ::std::string indent_;
+  ::std::string current_indent_;
+  bool comments_ = false;
+  bool multiline_ = false;
+  bool digit_grouping_ = false;
+  uint8_t numeric_base_ = 10;
+};
+
+namespace support {
+
+// TextOutputStream puts a stream-like interface onto a std::string, for use by
+// DumpToTextStream.  It is used by UpdateFromText().
+class TextOutputStream final {
+ public:
+  inline explicit TextOutputStream() = default;
+
+  inline void Write(const ::std::string &text) {
+    text_.write(text.data(), text.size());
+  }
+
+  inline void Write(const char *text) { text_.write(text, strlen(text)); }
+
+  inline void Write(const char c) { text_.put(c); }
+
+  inline ::std::string Result() { return text_.str(); }
+
+ private:
+  ::std::ostringstream text_;
+};
+
+// DecodeInteger decodes an integer from a string.  This is very similar to the
+// many, many existing integer decode routines in the world, except that a) it
+// accepts integers in any Emboss format, and b) it can run in environments that
+// do not support std::istream or Google's number conversion routines.
+//
+// Ideally, this would be replaced by someone else's code.
+template <class IntType>
+bool DecodeInteger(const ::std::string &text, IntType *result) {
+  IntType accumulator = 0;
+  IntType base = 10;
+  bool negative = false;
+  unsigned offset = 0;
+  if (::std::is_signed<IntType>::value && text.size() >= 1 + offset &&
+      text[offset] == '-') {
+    negative = true;
+    offset += 1;
+  }
+  if (text.size() >= 2 + offset && text[offset] == '0') {
+    if (text[offset + 1] == 'x' || text[offset + 1] == 'X') {
+      base = 16;
+      offset += 2;
+    } else if (text[offset + 1] == 'b' || text[offset + 1] == 'B') {
+      base = 2;
+      offset += 2;
+    }
+  }
+  // "", "0x", "0b", "-", "-0x", and "-0b" are not valid numbers.
+  if (offset == text.size()) return false;
+  for (; offset < text.size(); ++offset) {
+    char c = text[offset];
+    IntType digit = 0;
+    if (c == '_') {
+      if (offset == 0) {
+        return false;
+      }
+      continue;
+    } else if (c >= '0' && c <= '9') {
+      digit = c - '0';
+    } else if (c >= 'A' && c <= 'F') {
+      digit = c - 'A' + 10;
+    } else if (c >= 'a' && c <= 'f') {
+      digit = c - 'a' + 10;
+    } else {
+      return false;
+    }
+    if (digit >= base) {
+      return false;
+    }
+    if (negative) {
+      if (accumulator <
+          (::std::numeric_limits<IntType>::min() + digit) / base) {
+        return false;
+      }
+      accumulator = accumulator * base - digit;
+    } else {
+      if (accumulator >
+          (::std::numeric_limits<IntType>::max() - digit) / base) {
+        return false;
+      }
+      accumulator = accumulator * base + digit;
+    }
+  }
+  *result = accumulator;
+  return true;
+}
+
+template <class Stream>
+bool DiscardWhitespace(Stream *stream) {
+  char c;
+  bool in_comment = false;
+  do {
+    if (!stream->Read(&c)) return true;
+    if (c == '#') in_comment = true;
+    if (c == '\r' || c == '\n') in_comment = false;
+  } while (in_comment || c == ' ' || c == '\t' || c == '\n' || c == '\r');
+  return stream->Unread(c);
+}
+
+template <class Stream>
+bool ReadToken(Stream *stream, ::std::string *token) {
+  ::std::vector<char> result;
+  char c;
+  if (!DiscardWhitespace(stream)) return false;
+  if (!stream->Read(&c)) {
+    *token = "";
+    return true;
+  }
+
+  const char *const punctuation = ":{}[],";
+  if (strchr(punctuation, c) != nullptr) {
+    *token = ::std::string(1, c);
+    return true;
+  } else {
+    // TODO(bolms): Only allow alphanumeric characters here?
+    do {
+      result.push_back(c);
+      if (!stream->Read(&c)) {
+        *token = ::std::string(&result[0], result.size());
+        return true;
+      }
+    } while (c != ' ' && c != '\t' && c != '\n' && c != '\r' && c != '#' &&
+             strchr(punctuation, c) == nullptr);
+    if (!stream->Unread(c)) return false;
+    *token = ::std::string(&result[0], result.size());
+    return true;
+  }
+}
+
+template <class Stream, class View>
+bool ReadIntegerFromTextStream(View *view, Stream *stream) {
+  ::std::string token;
+  if (!::emboss::support::ReadToken(stream, &token)) return false;
+  if (token.empty()) return false;
+  typename View::ValueType value;
+  if (!::emboss::support::DecodeInteger(token, &value)) return false;
+  return view->TryToWrite(value);
+}
+
+// WriteIntegerToTextStream encodes the given value in base 2, 10, or 16, with
+// or without digit group separators ('_'), and then calls stream->Write() with
+// a char * argument that is a C-style null-terminated string of the encoded
+// number.
+//
+// As with DecodeInteger, above, it would be nice to be able to replace this
+// with someone else's code, but I (bolms@) was unable to find anything in
+// standard C++ that would encode numbers in binary, nothing that would add
+// digit separators to hex numbers, and nothing that would use '_' for digit
+// separators.
+template <class Stream, typename IntegralType>
+void WriteIntegerToTextStream(IntegralType value, Stream *stream, uint8_t base,
+                              bool digit_grouping) {
+  static_assert(::std::numeric_limits<
+                    typename ::std::remove_cv<IntegralType>::type>::is_integer,
+                "WriteIntegerToTextStream only supports integer types.");
+  static_assert(
+      !::std::is_same<bool,
+                      typename ::std::remove_cv<IntegralType>::type>::value,
+      "WriteIntegerToTextStream only supports integer types.");
+  EMBOSS_CHECK(base == 10 || base == 2 || base == 16);
+  const char *const digits = "0123456789abcdef";
+  const int grouping = base == 10 ? 3 : base == 16 ? 4 : 8;
+  // The maximum size 32-bit number is -2**31, which is:
+  //
+  // -0b10000000_00000000_00000000_00000000  (38 chars)
+  // -2_147_483_648  (14 chars)
+  // -0x8000_0000  (12 chars)
+  //
+  // Likewise, the maximum size 8-bit number is -128, which is:
+  // -0b10000000  (11 chars)
+  // -128  (4 chars)
+  // -0x80  (5 chars)
+  //
+  // Binary with separators is always the longest value: 9 chars per 8 bits,
+  // minus 1 char for the '_' that does not appear at the front of the number,
+  // plus 2 chars for "0b", plus 1 char for '-', plus 1 extra char for the
+  // trailing '\0', which is (sizeof value) * CHAR_BIT * 9 / 8 - 1 + 2 + 1 + 1.
+  const int buffer_size = (sizeof value) * CHAR_BIT * 9 / 8 + 3;
+  char buffer[buffer_size];
+  buffer[buffer_size - 1] = '\0';
+  int next_char = buffer_size - 2;
+  if (value == 0) {
+    EMBOSS_DCHECK_GE(next_char, 0);
+    buffer[next_char] = digits[0];
+    --next_char;
+  }
+  int sign = value < 0 ? -1 : 1;
+  int digit_count = 0;
+  auto buffer_char = [&](char c) {
+    EMBOSS_DCHECK_GE(next_char, 0);
+    buffer[next_char] = c;
+    --next_char;
+  };
+  if (value < 0) {
+    if (value == ::std::numeric_limits<decltype(value)>::lowest()) {
+      // The minimum negative two's-complement value has no corresponding
+      // positive value, so 'value = -value' is not useful in that case.
+      // Instead, we do some trickery to buffer the lowest-order digit here.
+      auto digit = -(value + 1) % base + 1;
+      value = -(value + 1) / base;
+      if (digit == base) {
+        digit = 0;
+        ++value;
+      }
+      buffer_char(digits[digit]);
+      ++digit_count;
+    } else {
+      value = -value;
+    }
+  }
+  while (value > 0) {
+    if (digit_count && digit_count % grouping == 0 && digit_grouping) {
+      buffer_char('_');
+    }
+    buffer_char(digits[value % base]);
+    value /= base;
+    ++digit_count;
+  }
+  if (base == 16) {
+    buffer_char('x');
+    buffer_char('0');
+  } else if (base == 2) {
+    buffer_char('b');
+    buffer_char('0');
+  }
+  if (sign < 0) {
+    buffer_char('-');
+  }
+
+  stream->Write(buffer + 1 + next_char);
+}
+
+// Writes an integer value in the base given in options, plus an optional
+// comment with the same value in a second base.  This is used for the common
+// output format of IntView, UIntView, and BcdView.
+template <class Stream, class View>
+void WriteIntegerViewToTextStream(View *view, Stream *stream,
+                                  const TextOutputOptions &options) {
+  WriteIntegerToTextStream(view->Read(), stream, options.numeric_base(),
+                           options.digit_grouping());
+  if (options.comments()) {
+    stream->Write("  # ");
+    WriteIntegerToTextStream(view->Read(), stream,
+                             options.numeric_base() == 10 ? 16 : 10,
+                             options.digit_grouping());
+  }
+}
+
+// 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.
+template <class Stream, class View>
+void WriteBooleanViewToTextStream(View *view, Stream *stream,
+                                  const TextOutputOptions &) {
+  if (view->Read()) {
+    stream->Write("true");
+  } else {
+    stream->Write("false");
+  }
+}
+
+// FloatConstants holds various masks for working with IEEE754-compatible
+// floating-point values at a bit level.  These are mostly used here to
+// implement text format for NaNs, preserving the NaN payload so that the text
+// format can (in theory) provide a bit-exact round-trip through the text
+// format.
+template <class Float>
+struct FloatConstants;
+
+template <>
+struct FloatConstants<float> {
+  static_assert(sizeof(float) == 4, "Emboss requires 32-bit float.");
+  using MatchingIntegerType = ::std::uint32_t;
+  static constexpr MatchingIntegerType kMantissaMask() { return 0x7fffffU; }
+  static constexpr MatchingIntegerType kExponentMask() { return 0x7f800000U; }
+  static constexpr MatchingIntegerType kSignMask() { return 0x80000000U; }
+  static constexpr int kPrintfPrecision() { return 9; }
+  static constexpr const char *kScanfFormat() { return "%f%n"; }
+};
+
+template <>
+struct FloatConstants<double> {
+  static_assert(sizeof(double) == 8, "Emboss requires 64-bit double.");
+  using MatchingIntegerType = ::std::uint64_t;
+  static constexpr MatchingIntegerType kMantissaMask() { return 0xfffffffffffffUL; }
+  static constexpr MatchingIntegerType kExponentMask() { return 0x7ff0000000000000UL; }
+  static constexpr MatchingIntegerType kSignMask() { return 0x8000000000000000UL; }
+  static constexpr int kPrintfPrecision() { return 17; }
+  static constexpr const char *kScanfFormat() { return "%lf%n"; }
+};
+
+// Decodes a floating-point number from text.
+template <class Float>
+bool DecodeFloat(const ::std::string &token, Float *result) {
+  // The state of the world for reading floating-point values is somewhat better
+  // than the situation for writing them, but there are still a few bits that
+  // are underspecified.  This function is the mirror of WriteFloatToTextStream,
+  // below, so it specifically decodes infinities and NaNs in the formats that
+  // Emboss uses.
+  //
+  // Because of the use of scanf here, this function accepts hex floating-point
+  // values (0xh.hhhhpeee) *on some systems*.  TODO(bolms): make hex float
+  // support universal.
+
+  using UInt = typename FloatConstants<Float>::MatchingIntegerType;
+
+  if (token.empty()) return false;
+
+  // First, check for negative.
+  bool negative = token[0] == '-';
+
+  // Second, check for NaN.
+  ::std::size_t i = token[0] == '-' || token[0] == '+' ? 1 : 0;
+  if (token.size() >= i + 3 && (token[i] == 'N' || token[i] == 'n') &&
+      (token[i + 1] == 'A' || token[i + 1] == 'a') &&
+      (token[i + 2] == 'N' || token[i + 2] == 'n')) {
+    UInt nan_payload;
+    if (token.size() >= i + 4) {
+      if (token[i + 3] == '(' && token[token.size() - 1] == ')') {
+        if (!DecodeInteger(token.substr(i + 4, token.size() - i - 5),
+                           &nan_payload)) {
+          return false;
+        }
+      } else {
+        // NaN may not be followed by trailing characters other than a
+        // ()-enclosed payload.
+        return false;
+      }
+    } else {
+      // If no specific NaN was given, take a default NaN from the C++ standard
+      // library.  Technically, a conformant C++ implementation might not have
+      // quiet_NaN(), but any IEEE754-based implementation should.
+      //
+      // It is tempting to just write the default NaN directly into the view and
+      // return success, but "-NaN" should be have its sign bit set, and there
+      // is no direct way to set the sign bit of a NaN, so there are fewer code
+      // paths if we extract the default NaN payload, then use it in the
+      // reconstruction step, below.
+      Float default_nan = ::std::numeric_limits<Float>::quiet_NaN();
+      UInt bits;
+      ::std::memcpy(&bits, &default_nan, sizeof(bits));
+      nan_payload = bits & FloatConstants<Float>::kMantissaMask();
+    }
+    if (nan_payload == 0) {
+      // "NaN" with a payload of zero is actually the bit pattern for infinity;
+      // "NaN(0)" should not be an alias for "Inf".
+      return false;
+    }
+    if (nan_payload & (FloatConstants<Float>::kExponentMask() |
+                       FloatConstants<Float>::kSignMask())) {
+      // The payload must be small enough to fit in the payload space; it must
+      // not overflow into the exponent or sign bits.
+      //
+      // Note that the DecodeInteger call which decoded the payload will return
+      // false if the payload would overflow the `UInt` type, so cases like
+      // "NaN(0x10000000000000000000000000000)" -- which are so big that they no
+      // longer interfere with the sign or exponent -- are caught above.
+      return false;
+    }
+    UInt bits = FloatConstants<Float>::kExponentMask();
+    bits |= nan_payload;
+    if (negative) {
+      bits |= FloatConstants<Float>::kSignMask();
+    }
+    ::std::memcpy(result, &bits, sizeof(bits));
+    return true;
+  }
+
+  // If the value is not NaN, check for infinity.
+  if (token.size() >= i + 3 && (token[i] == 'I' || token[i] == 'i') &&
+      (token[i + 1] == 'N' || token[i + 1] == 'n') &&
+      (token[i + 2] == 'F' || token[i + 2] == 'f')) {
+    if (token.size() > i + 3) {
+      // Infinity must be exactly "Inf" or "-Inf" (case insensitive).  There
+      // must not be trailing characters.
+      return false;
+    }
+    // As with quiet_NaN(), a conforming C++ implementation might not have
+    // infinity(), but an IEEE 754-based implementation should.
+    if (negative) {
+      *result = -::std::numeric_limits<Float>::infinity();
+      return true;
+    } else {
+      *result = ::std::numeric_limits<Float>::infinity();
+      return true;
+    }
+  }
+
+  // For non-NaN, non-Inf values, use the C scanf function, mirroring the use of
+  // printf for writing the value, below.
+  int chars_used = -1;
+  if (::std::sscanf(token.c_str(), FloatConstants<Float>::kScanfFormat(), result,
+                    &chars_used) < 1) {
+    return false;
+  }
+  if (chars_used < 0 ||
+      static_cast</**/ ::std::size_t>(chars_used) < token.size()) {
+    return false;
+  }
+  return true;
+}
+
+// Decodes a floating-point number from a text stream and writes it to the
+// specified view.
+template <class Stream, class View>
+bool ReadFloatFromTextStream(View *view, Stream *stream) {
+  ::std::string token;
+  if (!ReadToken(stream, &token)) return false;
+  typename View::ValueType value;
+  if (!DecodeFloat(token, &value)) return false;
+  return view->TryToWrite(value);
+}
+
+template <class Stream, class Float>
+void WriteFloatToTextStream(Float n, Stream *stream,
+                            const TextOutputOptions &options) {
+  static_assert(::std::is_same<Float, float>::value ||
+                    ::std::is_same<Float, double>::value,
+                "WriteFloatToTextStream can only write float or double.");
+  // The state of the world w.r.t. rendering floating-points as decimal text is,
+  // ca. 2018, less than ideal.
+  //
+  // In C++ land, there is actually no stable facility in the standard library
+  // until to_chars() in C++17 -- which is not actually implemented yet in
+  // libc++.  to_string(), the printf() family, and the iostreams system all
+  // respect the current locale.  In most programs, the locale is permanently
+  // left on "C", but this is not guaranteed.  to_string() also uses a fixed and
+  // rather unfortunate format.
+  //
+  // For integers, I (bolms@) chose to just implement custom read and write
+  // routines, but those routines are quite small and straightforward compared
+  // to floating point conversion.  Even writing correct output is difficult,
+  // and writing correct and minimal output is the subject of a number of
+  // academic papers.
+  //
+  // For the moment, I'm just using snprintf("%.*g", 17, n), which is guaranteed
+  // to be read back as the same number, but can be longer than strictly
+  // necessary.
+  //
+  // TODO(bolms): Import a modified version of the double-to-string conversion
+  // from Swift's standard library, which appears to be best implementation
+  // currently available.
+
+  if (::std::isnan(n)) {
+    // The printf format for NaN is just "NaN".  In the interests of keeping
+    // things bit-exact, Emboss prints the exact NaN.
+    typename FloatConstants<Float>::MatchingIntegerType bits;
+    ::std::memcpy(&bits, &n, sizeof(bits));
+    ::std::uint64_t nan_payload = bits & FloatConstants<Float>::kMantissaMask();
+    ::std::uint64_t nan_sign = bits & FloatConstants<Float>::kSignMask();
+    if (nan_sign) {
+      // NaN still has a sign bit, which is generally treated differently from
+      // the payload.  There is no real "standard" text format for NaNs, but
+      // "-NaN" appears to be a common way of indicating a NaN with the sign bit
+      // set.
+      stream->Write("-NaN(");
+    } else {
+      stream->Write("NaN(");
+    }
+    // NaN payloads are always dumped in hex.  Note that Emboss is treating the
+    // is_quiet/is_signal bit as just another bit in the payload.
+    WriteIntegerToTextStream(nan_payload, stream, 16, options.digit_grouping());
+    stream->Write(")");
+    return;
+  }
+
+  if (::std::isinf(n)) {
+    if (n < 0.0) {
+      stream->Write("-Inf");
+    } else {
+      stream->Write("Inf");
+    }
+    return;
+  }
+
+  // TODO(bolms): Should the current numeric base be honored here?  Should there
+  // be a separate Float numeric base?
+  ::std::array<char, 30> buffer;
+  // TODO(bolms): Figure out how to get ::std::snprintf to work on
+  // microcontroller builds.
+  EMBOSS_CHECK_LE(static_cast</**/ ::std::size_t>(
+                      ::snprintf(&(buffer[0]), buffer.size(), "%.*g",
+                                 FloatConstants<Float>::kPrintfPrecision(),
+                                 static_cast<double>(n)) +
+                      1),
+                  buffer.size());
+  stream->Write(&buffer[0]);
+
+  // TODO(bolms): Support digit grouping.
+}
+
+template <class Stream, class View>
+void WriteEnumViewToTextStream(View *view, Stream *stream,
+                               const TextOutputOptions &options) {
+  const char *name = TryToGetNameFromEnum(view->Read());
+  if (name != nullptr) {
+    stream->Write(name);
+  }
+  // If the enum value has no known name, then write its numeric value
+  // instead.  If it does have a known name, and comments are enabled on the
+  // output, then write the numeric value as a comment.
+  if (name == nullptr || options.comments()) {
+    if (name != nullptr) stream->Write("  # ");
+    WriteIntegerToTextStream(
+        static_cast<
+            typename ::std::underlying_type<typename View::ValueType>::type>(
+            view->Read()),
+        stream, options.numeric_base(), options.digit_grouping());
+  }
+}
+
+// Updates an array from a text stream.  For an array of integers, the most
+// basic form of the text format looks like:
+//
+// { 0, 1, 2 }
+//
+// However, the following are all acceptable and equivalent:
+//
+// { 0, 1, 2, }
+// {0 1 2}
+// { [2]: 2, [1]: 1, [0]: 0 }
+// {[2]:2, [0]:0, 1}
+//
+// Formally, the array must be contained within braces ("{}").  Elements are
+// represented as an optional index surrounded by brackets ("[]") followed by
+// the text format of the element, followed by a single optional comma (",").
+// If no index is present for the first element, the index 0 will be used.  If
+// no index is present for any elements after the first, the index one greater
+// than the previous index will be used.
+template <class Array, class Stream>
+bool ReadArrayFromTextStream(Array *array, Stream *stream) {
+  // The text format allows any given index to be set more than once.  In
+  // theory, this function could track indices and fail if an index were
+  // double-set, but doing so would require quite a bit of overhead, and
+  // O(array->ElementCount()) extra space in the worst case.  It does not seem
+  // worth it to impose the runtime cost here.
+  size_t index = 0;
+  ::std::string brace;
+  // Read out the opening brace.
+  if (!ReadToken(stream, &brace)) return false;
+  if (brace != "{") return false;
+  for (;;) {
+    char c;
+    // Check for a closing brace; if present, success.
+    if (!DiscardWhitespace(stream)) return false;
+    if (!stream->Read(&c)) return false;
+    if (c == '}') return true;
+
+    // If the element has an index, read it.
+    if (c == '[') {
+      ::std::string index_text;
+      if (!ReadToken(stream, &index_text)) return false;
+      if (!::emboss::support::DecodeInteger(index_text, &index)) return false;
+      ::std::string closing_bracket;
+      if (!ReadToken(stream, &closing_bracket)) return false;
+      if (closing_bracket != "]") return false;
+      ::std::string colon;
+      if (!ReadToken(stream, &colon)) return false;
+      if (colon != ":") return false;
+    } else {
+      if (!stream->Unread(c)) return false;
+    }
+
+    // Read the element.
+    if (index >= array->ElementCount()) return false;
+    if (!(*array)[index].UpdateFromTextStream(stream)) return false;
+    ++index;
+
+    // If there is a trailing comma, discard it.
+    if (!DiscardWhitespace(stream)) return false;
+    if (!stream->Read(&c)) return false;
+    if (c != ',') {
+      if (c != '}') return false;
+      if (!stream->Unread(c)) return false;
+    }
+  }
+}
+
+// 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.
+//
+// TODO(bolms): Make the output for arrays of small elements (like bytes) much
+// more compact.
+//
+// This will require several support functions like `MaxTextLength` on every
+// view type, and will substantially increase the number of tests required for
+// this function, but will make arrays of small elements much more readable.
+template <class Array, class Stream>
+void WriteArrayToTextStream(Array *array, Stream *stream,
+                            const TextOutputOptions &options) {
+  TextOutputOptions element_options = options.PlusOneIndent();
+  if (options.multiline()) {
+    stream->Write("{");
+    WriteShorthandArrayCommentToTextStream(array, stream, element_options);
+    for (::std::size_t i = 0; i < array->ElementCount(); ++i) {
+      stream->Write("\n");
+      stream->Write(element_options.current_indent());
+      stream->Write("[");
+      // TODO(bolms): Put padding in here so that array elements start at the
+      // same column.
+      //
+      // TODO(bolms): (Maybe) figure out how to get padding to work so that
+      // elements with comments can have their comments align to the same
+      // column.
+      WriteIntegerToTextStream(i, stream, options.numeric_base(),
+                               options.digit_grouping());
+      stream->Write("]: ");
+      (*array)[i].WriteToTextStream(stream, element_options);
+    }
+    stream->Write("\n");
+    stream->Write(options.current_indent());
+    stream->Write("}");
+  } else {
+    stream->Write("{");
+    for (::std::size_t i = 0; i < array->ElementCount(); ++i) {
+      stream->Write(" ");
+      if (i % 8 == 0) {
+        stream->Write("[");
+        WriteIntegerToTextStream(i, stream, options.numeric_base(),
+                                 options.digit_grouping());
+        stream->Write("]: ");
+      }
+      (*array)[i].WriteToTextStream(stream, element_options);
+      if (i < array->ElementCount() - 1) {
+        stream->Write(",");
+      }
+    }
+    stream->Write(" }");
+  }
+}
+
+// TextStream puts a stream-like interface onto a std::string, for use by
+// UpdateFromTextStream.  It is used by UpdateFromText().
+class TextStream final {
+ public:
+  // This template handles std::string, std::string_view, and absl::string_view.
+  template <class String>
+  inline explicit TextStream(const String &text)
+      : text_(text.data()), length_(text.size()) {}
+
+  inline explicit TextStream(const char *text)
+      : text_(text), length_(strlen(text)) {}
+
+  inline TextStream(const char *text, size_t length)
+      : text_(text), length_(length) {}
+
+  inline bool Read(char *result) {
+    if (index_ >= length_) return false;
+    *result = text_[index_];
+    ++index_;
+    return true;
+  }
+
+  inline bool Unread(char c) {
+    if (index_ < 1) return false;
+    if (text_[index_ - 1] != c) return false;
+    --index_;
+    return true;
+  }
+
+ private:
+  // It would be nice to use string_view here, but that's not available until
+  // C++17.
+  const char *text_ = nullptr;
+  size_t length_ = 0;
+  size_t index_ = 0;
+};
+
+}  // namespace support
+
+// Returns a TextOutputOptions set for reasonable multi-line text output.
+static inline TextOutputOptions MultilineText() {
+  return TextOutputOptions()
+      .Multiline(true)
+      .WithIndent("  ")
+      .WithComments(true)
+      .WithDigitGrouping(true);
+}
+
+// TODO(bolms): Add corresponding ReadFromText*() verbs which enforce the
+// constraint that all of a field's dependencies must be present in the text
+// before the field itself is set.
+template <typename EmbossViewType>
+inline bool UpdateFromText(const EmbossViewType &view,
+                           const ::std::string &text) {
+  auto text_stream = support::TextStream{text};
+  return view.UpdateFromTextStream(&text_stream);
+}
+
+template <typename EmbossViewType>
+inline ::std::string WriteToString(const EmbossViewType &view,
+                                   TextOutputOptions options) {
+  support::TextOutputStream text_stream;
+  view.WriteToTextStream(&text_stream, options);
+  return text_stream.Result();
+}
+
+template <typename EmbossViewType>
+inline ::std::string WriteToString(const EmbossViewType &view) {
+  return WriteToString(view, TextOutputOptions());
+}
+
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_TEXT_UTIL_H_
diff --git a/public/emboss_text_util_test.cc b/public/emboss_text_util_test.cc
new file mode 100644
index 0000000..4a0e4eb
--- /dev/null
+++ b/public/emboss_text_util_test.cc
@@ -0,0 +1,1114 @@
+// 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.
+
+#include "public/emboss_text_util.h"
+
+#include <cmath>
+#include <limits>
+
+#include <gtest/gtest.h>
+
+namespace emboss {
+namespace support {
+namespace test {
+
+TEST(DecodeInteger, DecodeUInt8Decimal) {
+  uint8_t result;
+  EXPECT_TRUE(DecodeInteger("123", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_TRUE(DecodeInteger("0", &result));
+  EXPECT_EQ(0, result);
+  EXPECT_TRUE(DecodeInteger("0123", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_TRUE(DecodeInteger("0_123", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("_12", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("1234", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12a", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12A", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12 ", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger(" 12", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12.", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12.0", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("256", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_TRUE(DecodeInteger("128", &result));
+  EXPECT_EQ(128, result);
+  EXPECT_FALSE(DecodeInteger("-0", &result));
+  EXPECT_EQ(128, result);
+  EXPECT_TRUE(DecodeInteger("255", &result));
+  EXPECT_EQ(255, result);
+}
+
+TEST(DecodeInteger, DecodeInt8Decimal) {
+  int8_t result;
+  EXPECT_TRUE(DecodeInteger("123", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_TRUE(DecodeInteger("0", &result));
+  EXPECT_EQ(0, result);
+  EXPECT_TRUE(DecodeInteger("0123", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_TRUE(DecodeInteger("0_123", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("_12", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("1234", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12a", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12A", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12 ", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger(" 12", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12.", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("12.0", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("256", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_FALSE(DecodeInteger("128", &result));
+  EXPECT_EQ(123, result);
+  EXPECT_TRUE(DecodeInteger("-0", &result));
+  EXPECT_EQ(0, result);
+  EXPECT_TRUE(DecodeInteger("127", &result));
+  EXPECT_EQ(127, result);
+  EXPECT_TRUE(DecodeInteger("-127", &result));
+  EXPECT_EQ(-127, result);
+  EXPECT_TRUE(DecodeInteger("-128", &result));
+  EXPECT_EQ(-128, result);
+  EXPECT_FALSE(DecodeInteger("0-127", &result));
+  EXPECT_EQ(-128, result);
+  EXPECT_FALSE(DecodeInteger("- 127", &result));
+  EXPECT_EQ(-128, result);
+}
+
+TEST(DecodeInteger, DecodeUInt8Hex) {
+  uint8_t result;
+  EXPECT_TRUE(DecodeInteger("0x23", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_TRUE(DecodeInteger("0x0", &result));
+  EXPECT_EQ(0x0, result);
+  EXPECT_TRUE(DecodeInteger("0xff", &result));
+  EXPECT_EQ(0xff, result);
+  EXPECT_TRUE(DecodeInteger("0xFE", &result));
+  EXPECT_EQ(0xfe, result);
+  EXPECT_TRUE(DecodeInteger("0xFd", &result));
+  EXPECT_EQ(0xfd, result);
+  EXPECT_TRUE(DecodeInteger("0XeC", &result));
+  EXPECT_EQ(0xec, result);
+  EXPECT_TRUE(DecodeInteger("0x012", &result));
+  EXPECT_EQ(0x12, result);
+  EXPECT_TRUE(DecodeInteger("0x0_0023", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_TRUE(DecodeInteger("0x_0023", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0x100", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0x", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0x0x0", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0x1g", &result));
+  EXPECT_EQ(0x23, result);
+}
+
+TEST(DecodeInteger, DecodeUInt8Binary) {
+  uint8_t result;
+  EXPECT_TRUE(DecodeInteger("0b10100101", &result));
+  EXPECT_EQ(0xa5, result);
+  EXPECT_TRUE(DecodeInteger("0b0", &result));
+  EXPECT_EQ(0x0, result);
+  EXPECT_TRUE(DecodeInteger("0B1", &result));
+  EXPECT_EQ(0x1, result);
+  EXPECT_TRUE(DecodeInteger("0b11111111", &result));
+  EXPECT_EQ(0xff, result);
+  EXPECT_TRUE(DecodeInteger("0b011111110", &result));
+  EXPECT_EQ(0xfe, result);
+  EXPECT_TRUE(DecodeInteger("0b00_0010_0011", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_TRUE(DecodeInteger("0b_0010_0011", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b100000000", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b0b0", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b12", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("-0b0", &result));
+  EXPECT_EQ(0x23, result);
+}
+
+TEST(DecodeInteger, DecodeInt8Binary) {
+  int8_t result;
+  EXPECT_TRUE(DecodeInteger("0b01011010", &result));
+  EXPECT_EQ(0x5a, result);
+  EXPECT_TRUE(DecodeInteger("0b0", &result));
+  EXPECT_EQ(0x0, result);
+  EXPECT_TRUE(DecodeInteger("0B1", &result));
+  EXPECT_EQ(0x1, result);
+  EXPECT_TRUE(DecodeInteger("0b1111111", &result));
+  EXPECT_EQ(0x7f, result);
+  EXPECT_TRUE(DecodeInteger("0b01111110", &result));
+  EXPECT_EQ(0x7e, result);
+  EXPECT_TRUE(DecodeInteger("0b00_0010_0011", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_TRUE(DecodeInteger("0b_0010_0011", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b100000000", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("-0b", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b0b0", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b12", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_FALSE(DecodeInteger("0b10000000", &result));
+  EXPECT_EQ(0x23, result);
+  EXPECT_TRUE(DecodeInteger("-0b1111111", &result));
+  EXPECT_EQ(-0x7f, result);
+  EXPECT_TRUE(DecodeInteger("-0b10000000", &result));
+  EXPECT_EQ(-0x80, result);
+  EXPECT_FALSE(DecodeInteger("-0b10000001", &result));
+  EXPECT_EQ(-0x80, result);
+  EXPECT_TRUE(DecodeInteger("-0b0", &result));
+  EXPECT_EQ(0x0, result);
+}
+
+TEST(DecodeInteger, DecodeUInt16) {
+  uint16_t result;
+  EXPECT_TRUE(DecodeInteger("65535", &result));
+  EXPECT_EQ(65535, result);
+  EXPECT_FALSE(DecodeInteger("65536", &result));
+  EXPECT_EQ(65535, result);
+}
+
+TEST(DecodeInteger, DecodeInt16) {
+  int16_t result;
+  EXPECT_TRUE(DecodeInteger("32767", &result));
+  EXPECT_EQ(32767, result);
+  EXPECT_FALSE(DecodeInteger("32768", &result));
+  EXPECT_EQ(32767, result);
+  EXPECT_TRUE(DecodeInteger("-32768", &result));
+  EXPECT_EQ(-32768, result);
+  EXPECT_FALSE(DecodeInteger("-32769", &result));
+  EXPECT_EQ(-32768, result);
+}
+
+TEST(DecodeInteger, DecodeUInt32) {
+  uint32_t result;
+  EXPECT_TRUE(DecodeInteger("4294967295", &result));
+  EXPECT_EQ(4294967295U, result);
+  EXPECT_FALSE(DecodeInteger("4294967296", &result));
+  EXPECT_EQ(4294967295U, result);
+}
+
+TEST(DecodeInteger, DecodeInt32) {
+  int32_t result;
+  EXPECT_TRUE(DecodeInteger("2147483647", &result));
+  EXPECT_EQ(2147483647, result);
+  EXPECT_FALSE(DecodeInteger("2147483648", &result));
+  EXPECT_EQ(2147483647, result);
+  EXPECT_FALSE(DecodeInteger("4294967295", &result));
+  EXPECT_EQ(2147483647, result);
+  EXPECT_TRUE(DecodeInteger("-2147483648", &result));
+  EXPECT_EQ(-2147483647 - 1, result);
+  EXPECT_FALSE(DecodeInteger("-2147483649", &result));
+  EXPECT_EQ(-2147483647 - 1, result);
+}
+
+TEST(DecodeInteger, DecodeUInt64) {
+  uint64_t result;
+  EXPECT_TRUE(DecodeInteger("18446744073709551615", &result));
+  EXPECT_EQ(18446744073709551615ULL, result);
+  EXPECT_FALSE(DecodeInteger("18446744073709551616", &result));
+  EXPECT_EQ(18446744073709551615ULL, result);
+}
+
+TEST(DecodeInteger, DecodeInt64) {
+  int64_t result;
+  EXPECT_TRUE(DecodeInteger("9223372036854775807", &result));
+  EXPECT_EQ(9223372036854775807LL, result);
+  EXPECT_FALSE(DecodeInteger("9223372036854775808", &result));
+  EXPECT_EQ(9223372036854775807LL, result);
+  EXPECT_FALSE(DecodeInteger("18446744073709551615", &result));
+  EXPECT_EQ(9223372036854775807LL, result);
+  EXPECT_TRUE(DecodeInteger("-9223372036854775808", &result));
+  EXPECT_EQ(-9223372036854775807LL - 1LL, result);
+  EXPECT_FALSE(DecodeInteger("-9223372036854775809", &result));
+  EXPECT_EQ(-9223372036854775807LL - 1LL, result);
+}
+
+TEST(TextStream, Construction) {
+  std::string string_text = "ab";
+  auto text_stream = TextStream(string_text);
+  char result;
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('a', result);
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('b', result);
+  EXPECT_FALSE(text_stream.Read(&result));
+
+  const char *c_string = "cd";
+  text_stream = TextStream(c_string);
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('c', result);
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('d', result);
+  EXPECT_FALSE(text_stream.Read(&result));
+
+  const char *long_c_string = "efghi";
+  text_stream = TextStream(long_c_string, 2);
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('e', result);
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('f', result);
+  EXPECT_FALSE(text_stream.Read(&result));
+}
+
+TEST(TextStream, Methods) {
+  auto text_stream = TextStream{"abc"};
+
+  EXPECT_FALSE(text_stream.Unread('d'));
+  char result;
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('a', result);
+
+  EXPECT_FALSE(text_stream.Unread('e'));
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('b', result);
+
+  EXPECT_TRUE(text_stream.Unread('b'));
+  result = 'f';
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('b', result);
+
+  EXPECT_TRUE(text_stream.Read(&result));
+  EXPECT_EQ('c', result);
+
+  result = 'g';
+  EXPECT_FALSE(text_stream.Read(&result));
+  EXPECT_EQ('g', result);
+
+  auto empty_text_stream = TextStream{""};
+  EXPECT_FALSE(empty_text_stream.Read(&result));
+  EXPECT_EQ('g', result);
+}
+
+TEST(ReadToken, ReadsToken) {
+  auto text_stream = TextStream{"abc"};
+  ::std::string result;
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("abc", result);
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("", result);
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("", result);
+}
+
+TEST(ReadToken, ReadsTwoTokens) {
+  auto text_stream = TextStream{"abc def"};
+  ::std::string result;
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("abc", result);
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("def", result);
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("", result);
+}
+
+TEST(ReadToken, SkipsInitialWhitespace) {
+  auto text_stream = TextStream{"  \t\r\r\n\t\r  abc def"};
+  ::std::string result;
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("abc", result);
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("def", result);
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("", result);
+}
+
+TEST(ReadToken, SkipsComments) {
+  auto text_stream = TextStream{"  #comment##\r#comment\n abc #c\n  def"};
+  ::std::string result;
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("abc", result);
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("def", result);
+  EXPECT_TRUE(ReadToken(&text_stream, &result));
+  EXPECT_EQ("", result);
+}
+
+TEST(TextOutputOptions, Defaults) {
+  TextOutputOptions options;
+  EXPECT_EQ("", options.current_indent());
+  EXPECT_EQ("", options.indent());
+  EXPECT_FALSE(options.multiline());
+  EXPECT_FALSE(options.comments());
+  EXPECT_FALSE(options.digit_grouping());
+  EXPECT_EQ(10, options.numeric_base());
+}
+
+TEST(TextOutputOptions, WithIndent) {
+  TextOutputOptions options;
+  TextOutputOptions new_options = options.WithIndent("xyz");
+  EXPECT_EQ("", options.current_indent());
+  EXPECT_EQ("", options.indent());
+  EXPECT_EQ("", new_options.current_indent());
+  EXPECT_EQ("xyz", new_options.indent());
+}
+
+TEST(TextOutputOptions, PlusOneIndent) {
+  TextOutputOptions options;
+  TextOutputOptions new_options = options.WithIndent("xyz").PlusOneIndent();
+  EXPECT_EQ("", options.current_indent());
+  EXPECT_EQ("", options.indent());
+  EXPECT_EQ("xyz", new_options.current_indent());
+  EXPECT_EQ("xyz", new_options.indent());
+  EXPECT_EQ("xyzxyz", new_options.PlusOneIndent().current_indent());
+}
+
+TEST(TextOutputOptions, WithComments) {
+  TextOutputOptions options;
+  TextOutputOptions new_options = options.WithComments(true);
+  EXPECT_FALSE(options.comments());
+  EXPECT_TRUE(new_options.comments());
+}
+
+TEST(TextOutputOptions, WithDigitGrouping) {
+  TextOutputOptions options;
+  TextOutputOptions new_options = options.WithDigitGrouping(true);
+  EXPECT_FALSE(options.digit_grouping());
+  EXPECT_TRUE(new_options.digit_grouping());
+}
+
+TEST(TextOutputOptions, Multiline) {
+  TextOutputOptions options;
+  TextOutputOptions new_options = options.Multiline(true);
+  EXPECT_FALSE(options.multiline());
+  EXPECT_TRUE(new_options.multiline());
+}
+
+TEST(TextOutputOptions, WithNumericBase) {
+  TextOutputOptions options;
+  TextOutputOptions new_options = options.WithNumericBase(2);
+  EXPECT_EQ(10, options.numeric_base());
+  EXPECT_EQ(2, new_options.numeric_base());
+}
+
+// Small helper function for the various WriteIntegerToTextStream tests; just
+// sets up a stream, forwards its arguments to WriteIntegerToTextStream, and
+// then returns the text from the stream.
+template <typename Arg0, typename... Args>
+::std::string WriteIntegerToString(Arg0 &&arg0, Args &&... args) {
+  TextOutputStream stream;
+  WriteIntegerToTextStream(::std::forward<Arg0>(arg0), &stream,
+                           ::std::forward<Args>(args)...);
+  return stream.Result();
+}
+
+TEST(WriteIntegerToTextStream, Decimal) {
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<uint8_t>(0), 10, false));
+  EXPECT_EQ("100", WriteIntegerToString(static_cast<uint8_t>(100), 10, false));
+  EXPECT_EQ("255", WriteIntegerToString(static_cast<uint8_t>(255), 10, false));
+  EXPECT_EQ("-128", WriteIntegerToString(static_cast<int8_t>(-128), 10, false));
+  EXPECT_EQ("-100", WriteIntegerToString(static_cast<int8_t>(-100), 10, false));
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<int8_t>(0), 10, false));
+  EXPECT_EQ("100", WriteIntegerToString(static_cast<int8_t>(100), 10, false));
+  EXPECT_EQ("127", WriteIntegerToString(static_cast<int8_t>(127), 10, false));
+
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<uint8_t>(0), 10, true));
+  EXPECT_EQ("100", WriteIntegerToString(static_cast<uint8_t>(100), 10, true));
+  EXPECT_EQ("255", WriteIntegerToString(static_cast<uint8_t>(255), 10, true));
+  EXPECT_EQ("-128", WriteIntegerToString(static_cast<int8_t>(-128), 10, true));
+  EXPECT_EQ("-100", WriteIntegerToString(static_cast<int8_t>(-100), 10, true));
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<int8_t>(0), 10, true));
+  EXPECT_EQ("100", WriteIntegerToString(static_cast<int8_t>(100), 10, true));
+  EXPECT_EQ("127", WriteIntegerToString(static_cast<int8_t>(127), 10, true));
+
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<uint16_t>(0), 10, false));
+  EXPECT_EQ("1000",
+            WriteIntegerToString(static_cast<uint16_t>(1000), 10, false));
+  EXPECT_EQ("65535",
+            WriteIntegerToString(static_cast<uint16_t>(65535), 10, false));
+  EXPECT_EQ("-32768",
+            WriteIntegerToString(static_cast<int16_t>(-32768), 10, false));
+  EXPECT_EQ("-10000",
+            WriteIntegerToString(static_cast<int16_t>(-10000), 10, false));
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<int16_t>(0), 10, false));
+  EXPECT_EQ("32767",
+            WriteIntegerToString(static_cast<int16_t>(32767), 10, false));
+
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<uint16_t>(0), 10, true));
+  EXPECT_EQ("999", WriteIntegerToString(static_cast<uint16_t>(999), 10, true));
+  EXPECT_EQ("1_000",
+            WriteIntegerToString(static_cast<uint16_t>(1000), 10, true));
+  EXPECT_EQ("65_535",
+            WriteIntegerToString(static_cast<uint16_t>(65535), 10, true));
+  EXPECT_EQ("-32_768",
+            WriteIntegerToString(static_cast<int16_t>(-32768), 10, true));
+  EXPECT_EQ("-1_000",
+            WriteIntegerToString(static_cast<int16_t>(-1000), 10, true));
+  EXPECT_EQ("-999", WriteIntegerToString(static_cast<int16_t>(-999), 10, true));
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<int16_t>(0), 10, true));
+  EXPECT_EQ("32_767",
+            WriteIntegerToString(static_cast<int16_t>(32767), 10, true));
+
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<uint32_t>(0), 10, false));
+  EXPECT_EQ("1000000",
+            WriteIntegerToString(static_cast<uint32_t>(1000000), 10, false));
+  EXPECT_EQ("4294967295",
+            WriteIntegerToString(static_cast<uint32_t>(4294967295), 10, false));
+  EXPECT_EQ("-2147483648",
+            WriteIntegerToString(static_cast<int32_t>(-2147483648), 10, false));
+  EXPECT_EQ("-100000",
+            WriteIntegerToString(static_cast<int32_t>(-100000), 10, false));
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<int32_t>(0), 10, false));
+  EXPECT_EQ("2147483647",
+            WriteIntegerToString(static_cast<int32_t>(2147483647), 10, false));
+
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<uint32_t>(0), 10, true));
+  EXPECT_EQ("999_999",
+            WriteIntegerToString(static_cast<uint32_t>(999999), 10, true));
+  EXPECT_EQ("1_000_000",
+            WriteIntegerToString(static_cast<uint32_t>(1000000), 10, true));
+  EXPECT_EQ("4_294_967_295",
+            WriteIntegerToString(static_cast<uint32_t>(4294967295U), 10, true));
+  EXPECT_EQ("-2_147_483_648",
+            WriteIntegerToString(static_cast<int32_t>(-2147483648L), 10, true));
+  EXPECT_EQ("-999_999",
+            WriteIntegerToString(static_cast<int32_t>(-999999), 10, true));
+  EXPECT_EQ("-1_000_000",
+            WriteIntegerToString(static_cast<int32_t>(-1000000), 10, true));
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<int32_t>(0), 10, true));
+  EXPECT_EQ("2_147_483_647",
+            WriteIntegerToString(static_cast<int32_t>(2147483647), 10, true));
+
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<uint64_t>(0), 10, false));
+  EXPECT_EQ("1000000",
+            WriteIntegerToString(static_cast<uint64_t>(1000000), 10, false));
+  EXPECT_EQ("18446744073709551615",
+            WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL),
+                                 10, false));
+  EXPECT_EQ("-9223372036854775808",
+            WriteIntegerToString(
+                static_cast<int64_t>(-9223372036854775807L - 1), 10, false));
+  EXPECT_EQ("-100000",
+            WriteIntegerToString(static_cast<int64_t>(-100000), 10, false));
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<int64_t>(0), 10, false));
+  EXPECT_EQ("9223372036854775807",
+            WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 10,
+                                 false));
+
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<uint64_t>(0), 10, true));
+  EXPECT_EQ("1_000_000",
+            WriteIntegerToString(static_cast<uint64_t>(1000000), 10, true));
+  EXPECT_EQ("18_446_744_073_709_551_615",
+            WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL),
+                                 10, true));
+  EXPECT_EQ("-9_223_372_036_854_775_808",
+            WriteIntegerToString(
+                static_cast<int64_t>(-9223372036854775807L - 1), 10, true));
+  EXPECT_EQ("-100_000",
+            WriteIntegerToString(static_cast<int64_t>(-100000), 10, true));
+  EXPECT_EQ("0", WriteIntegerToString(static_cast<int64_t>(0), 10, true));
+  EXPECT_EQ("9_223_372_036_854_775_807",
+            WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 10,
+                                 true));
+}
+
+TEST(WriteIntegerToTextStream, Binary) {
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint8_t>(0), 2, false));
+  EXPECT_EQ("0b1100100",
+            WriteIntegerToString(static_cast<uint8_t>(100), 2, false));
+  EXPECT_EQ("0b11111111",
+            WriteIntegerToString(static_cast<uint8_t>(255), 2, false));
+  EXPECT_EQ("-0b10000000",
+            WriteIntegerToString(static_cast<int8_t>(-128), 2, false));
+  EXPECT_EQ("-0b1100100",
+            WriteIntegerToString(static_cast<int8_t>(-100), 2, false));
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int8_t>(0), 2, false));
+  EXPECT_EQ("0b1100100",
+            WriteIntegerToString(static_cast<int8_t>(100), 2, false));
+  EXPECT_EQ("0b1111111",
+            WriteIntegerToString(static_cast<int8_t>(127), 2, false));
+
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint8_t>(0), 2, true));
+  EXPECT_EQ("0b1100100",
+            WriteIntegerToString(static_cast<uint8_t>(100), 2, true));
+  EXPECT_EQ("0b11111111",
+            WriteIntegerToString(static_cast<uint8_t>(255), 2, true));
+  EXPECT_EQ("-0b10000000",
+            WriteIntegerToString(static_cast<int8_t>(-128), 2, true));
+  EXPECT_EQ("-0b1100100",
+            WriteIntegerToString(static_cast<int8_t>(-100), 2, true));
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int8_t>(0), 2, true));
+  EXPECT_EQ("0b1100100",
+            WriteIntegerToString(static_cast<int8_t>(100), 2, true));
+  EXPECT_EQ("0b1111111",
+            WriteIntegerToString(static_cast<int8_t>(127), 2, true));
+
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint16_t>(0), 2, false));
+  EXPECT_EQ("0b1111101000",
+            WriteIntegerToString(static_cast<uint16_t>(1000), 2, false));
+  EXPECT_EQ("0b1111111111111111",
+            WriteIntegerToString(static_cast<uint16_t>(65535), 2, false));
+  EXPECT_EQ("-0b1000000000000000",
+            WriteIntegerToString(static_cast<int16_t>(-32768), 2, false));
+  EXPECT_EQ("-0b10011100010000",
+            WriteIntegerToString(static_cast<int16_t>(-10000), 2, false));
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int16_t>(0), 2, false));
+  EXPECT_EQ("0b111111111111111",
+            WriteIntegerToString(static_cast<int16_t>(32767), 2, false));
+
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint16_t>(0), 2, true));
+  EXPECT_EQ("0b11_11101000",
+            WriteIntegerToString(static_cast<uint16_t>(1000), 2, true));
+  EXPECT_EQ("0b11111111_11111111",
+            WriteIntegerToString(static_cast<uint16_t>(65535), 2, true));
+  EXPECT_EQ("-0b10000000_00000000",
+            WriteIntegerToString(static_cast<int16_t>(-32768), 2, true));
+  EXPECT_EQ("-0b11_11101000",
+            WriteIntegerToString(static_cast<int16_t>(-1000), 2, true));
+  EXPECT_EQ("-0b11_11100111",
+            WriteIntegerToString(static_cast<int16_t>(-999), 2, true));
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int16_t>(0), 2, true));
+  EXPECT_EQ("0b1111111_11111111",
+            WriteIntegerToString(static_cast<int16_t>(32767), 2, true));
+
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint32_t>(0), 2, false));
+  EXPECT_EQ("0b11110100001001000000",
+            WriteIntegerToString(static_cast<uint32_t>(1000000), 2, false));
+  EXPECT_EQ("0b11111111111111111111111111111111",
+            WriteIntegerToString(static_cast<uint32_t>(4294967295), 2, false));
+  EXPECT_EQ("-0b10000000000000000000000000000000",
+            WriteIntegerToString(static_cast<int32_t>(-2147483648), 2, false));
+  EXPECT_EQ("-0b11000011010100000",
+            WriteIntegerToString(static_cast<int32_t>(-100000), 2, false));
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int32_t>(0), 2, false));
+  EXPECT_EQ("0b1111111111111111111111111111111",
+            WriteIntegerToString(static_cast<int32_t>(2147483647), 2, false));
+
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint32_t>(0), 2, true));
+  EXPECT_EQ("0b1111_01000010_01000000",
+            WriteIntegerToString(static_cast<uint32_t>(1000000), 2, true));
+  EXPECT_EQ("0b11111111_11111111_11111111_11111111",
+            WriteIntegerToString(static_cast<uint32_t>(4294967295U), 2, true));
+  EXPECT_EQ("-0b10000000_00000000_00000000_00000000",
+            WriteIntegerToString(static_cast<int32_t>(-2147483648L), 2, true));
+  EXPECT_EQ("-0b1111_01000010_01000000",
+            WriteIntegerToString(static_cast<int32_t>(-1000000), 2, true));
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int32_t>(0), 2, true));
+  EXPECT_EQ("0b1111111_11111111_11111111_11111111",
+            WriteIntegerToString(static_cast<int32_t>(2147483647), 2, true));
+
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint64_t>(0), 2, false));
+  EXPECT_EQ("0b11110100001001000000",
+            WriteIntegerToString(static_cast<uint64_t>(1000000), 2, false));
+  EXPECT_EQ(
+      "0b1111111111111111111111111111111111111111111111111111111111111111",
+      WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL), 2,
+                           false));
+  EXPECT_EQ(
+      "-0b1000000000000000000000000000000000000000000000000000000000000000",
+      WriteIntegerToString(static_cast<int64_t>(-9223372036854775807L - 1), 2,
+                           false));
+  EXPECT_EQ("-0b11000011010100000",
+            WriteIntegerToString(static_cast<int64_t>(-100000), 2, false));
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int64_t>(0), 2, false));
+  EXPECT_EQ("0b111111111111111111111111111111111111111111111111111111111111111",
+            WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 2,
+                                 false));
+
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<uint64_t>(0), 2, true));
+  EXPECT_EQ("0b1111_01000010_01000000",
+            WriteIntegerToString(static_cast<uint64_t>(1000000), 2, true));
+  EXPECT_EQ(
+      "0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_"
+      "11111111",
+      WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL), 2,
+                           true));
+  EXPECT_EQ(
+      "-0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_"
+      "00000000",
+      WriteIntegerToString(static_cast<int64_t>(-9223372036854775807L - 1), 2,
+                           true));
+  EXPECT_EQ("-0b1_10000110_10100000",
+            WriteIntegerToString(static_cast<int64_t>(-100000), 2, true));
+  EXPECT_EQ("0b0", WriteIntegerToString(static_cast<int64_t>(0), 2, true));
+  EXPECT_EQ(
+      "0b1111111_11111111_11111111_11111111_11111111_11111111_11111111_"
+      "11111111",
+      WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 2,
+                           true));
+}
+
+TEST(WriteIntegerToTextStream, Hexadecimal) {
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint8_t>(0), 16, false));
+  EXPECT_EQ("0x64", WriteIntegerToString(static_cast<uint8_t>(100), 16, false));
+  EXPECT_EQ("0xff", WriteIntegerToString(static_cast<uint8_t>(255), 16, false));
+  EXPECT_EQ("-0x80",
+            WriteIntegerToString(static_cast<int8_t>(-128), 16, false));
+  EXPECT_EQ("-0x64",
+            WriteIntegerToString(static_cast<int8_t>(-100), 16, false));
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int8_t>(0), 16, false));
+  EXPECT_EQ("0x64", WriteIntegerToString(static_cast<int8_t>(100), 16, false));
+  EXPECT_EQ("0x7f", WriteIntegerToString(static_cast<int8_t>(127), 16, false));
+
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint8_t>(0), 16, true));
+  EXPECT_EQ("0x64", WriteIntegerToString(static_cast<uint8_t>(100), 16, true));
+  EXPECT_EQ("0xff", WriteIntegerToString(static_cast<uint8_t>(255), 16, true));
+  EXPECT_EQ("-0x80", WriteIntegerToString(static_cast<int8_t>(-128), 16, true));
+  EXPECT_EQ("-0x64", WriteIntegerToString(static_cast<int8_t>(-100), 16, true));
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int8_t>(0), 16, true));
+  EXPECT_EQ("0x64", WriteIntegerToString(static_cast<int8_t>(100), 16, true));
+  EXPECT_EQ("0x7f", WriteIntegerToString(static_cast<int8_t>(127), 16, true));
+
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint16_t>(0), 16, false));
+  EXPECT_EQ("0x3e8",
+            WriteIntegerToString(static_cast<uint16_t>(1000), 16, false));
+  EXPECT_EQ("0xffff",
+            WriteIntegerToString(static_cast<uint16_t>(65535), 16, false));
+  EXPECT_EQ("-0x8000",
+            WriteIntegerToString(static_cast<int16_t>(-32768), 16, false));
+  EXPECT_EQ("-0x2710",
+            WriteIntegerToString(static_cast<int16_t>(-10000), 16, false));
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int16_t>(0), 16, false));
+  EXPECT_EQ("0x7fff",
+            WriteIntegerToString(static_cast<int16_t>(32767), 16, false));
+
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint16_t>(0), 16, true));
+  EXPECT_EQ("0x3e8",
+            WriteIntegerToString(static_cast<uint16_t>(1000), 16, true));
+  EXPECT_EQ("0xffff",
+            WriteIntegerToString(static_cast<uint16_t>(65535), 16, true));
+  EXPECT_EQ("-0x8000",
+            WriteIntegerToString(static_cast<int16_t>(-32768), 16, true));
+  EXPECT_EQ("-0x3e8",
+            WriteIntegerToString(static_cast<int16_t>(-1000), 16, true));
+  EXPECT_EQ("-0x3e7",
+            WriteIntegerToString(static_cast<int16_t>(-999), 16, true));
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int16_t>(0), 16, true));
+  EXPECT_EQ("0x7fff",
+            WriteIntegerToString(static_cast<int16_t>(32767), 16, true));
+
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint32_t>(0), 16, false));
+  EXPECT_EQ("0xf4240",
+            WriteIntegerToString(static_cast<uint32_t>(1000000), 16, false));
+  EXPECT_EQ("0xffffffff",
+            WriteIntegerToString(static_cast<uint32_t>(4294967295), 16, false));
+  EXPECT_EQ("-0x80000000",
+            WriteIntegerToString(static_cast<int32_t>(-2147483648), 16, false));
+  EXPECT_EQ("-0x186a0",
+            WriteIntegerToString(static_cast<int32_t>(-100000), 16, false));
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int32_t>(0), 16, false));
+  EXPECT_EQ("0x7fffffff",
+            WriteIntegerToString(static_cast<int32_t>(2147483647), 16, false));
+
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint32_t>(0), 16, true));
+  EXPECT_EQ("0xf_4240",
+            WriteIntegerToString(static_cast<uint32_t>(1000000), 16, true));
+  EXPECT_EQ("0xffff_ffff",
+            WriteIntegerToString(static_cast<uint32_t>(4294967295U), 16, true));
+  EXPECT_EQ("-0x8000_0000",
+            WriteIntegerToString(static_cast<int32_t>(-2147483648L), 16, true));
+  EXPECT_EQ("-0xf_4240",
+            WriteIntegerToString(static_cast<int32_t>(-1000000), 16, true));
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int32_t>(0), 16, true));
+  EXPECT_EQ("0x7fff_ffff",
+            WriteIntegerToString(static_cast<int32_t>(2147483647), 16, true));
+
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint64_t>(0), 16, false));
+  EXPECT_EQ("0xf4240",
+            WriteIntegerToString(static_cast<uint64_t>(1000000), 16, false));
+  EXPECT_EQ("0xffffffffffffffff",
+            WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL),
+                                 16, false));
+  EXPECT_EQ("-0x8000000000000000",
+            WriteIntegerToString(
+                static_cast<int64_t>(-9223372036854775807L - 1), 16, false));
+  EXPECT_EQ("-0x186a0",
+            WriteIntegerToString(static_cast<int64_t>(-100000), 16, false));
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int64_t>(0), 16, false));
+  EXPECT_EQ("0x7fffffffffffffff",
+            WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 16,
+                                 false));
+
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<uint64_t>(0), 16, true));
+  EXPECT_EQ("0xf_4240",
+            WriteIntegerToString(static_cast<uint64_t>(1000000), 16, true));
+  EXPECT_EQ("0xffff_ffff_ffff_ffff",
+            WriteIntegerToString(static_cast<uint64_t>(18446744073709551615UL),
+                                 16, true));
+  EXPECT_EQ("-0x8000_0000_0000_0000",
+            WriteIntegerToString(
+                static_cast<int64_t>(-9223372036854775807L - 1), 16, true));
+  EXPECT_EQ("-0x1_86a0",
+            WriteIntegerToString(static_cast<int64_t>(-100000), 16, true));
+  EXPECT_EQ("0x0", WriteIntegerToString(static_cast<int64_t>(0), 16, true));
+  EXPECT_EQ("0x7fff_ffff_ffff_ffff",
+            WriteIntegerToString(static_cast<int64_t>(9223372036854775807L), 16,
+                                 true));
+}
+
+// Small helper function for the various WriteFloatToTextStream tests; just sets
+// up a stream, forwards its arguments to WriteFloatToTextStream, and then
+// returns the text from the stream.
+template <typename Arg0, typename... Args>
+::std::string WriteFloatToString(Arg0 &&arg0, Args &&... args) {
+  TextOutputStream stream;
+  WriteFloatToTextStream(::std::forward<Arg0>(arg0), &stream,
+                         ::std::forward<Args>(args)...);
+  return stream.Result();
+}
+
+TEST(WriteFloatToTextStream, RegularNumbers) {
+  EXPECT_EQ("0", WriteFloatToString(0.0, TextOutputOptions()));
+  EXPECT_EQ("1", WriteFloatToString(1.0, TextOutputOptions()));
+  EXPECT_EQ("1.5", WriteFloatToString(1.5, TextOutputOptions()));
+  // TODO(bolms): Figure out how to get minimal-length output.
+  EXPECT_EQ("1.6000000000000001", WriteFloatToString(1.6, TextOutputOptions()));
+  EXPECT_EQ("123456789", WriteFloatToString(123456789.0, TextOutputOptions()));
+  EXPECT_EQ("12345678901234568",
+            WriteFloatToString(12345678901234567.0, TextOutputOptions()));
+  EXPECT_EQ("-12345678901234568",
+            WriteFloatToString(-12345678901234567.0, TextOutputOptions()));
+  EXPECT_EQ("-1.2345678901234568e+17",
+            WriteFloatToString(-123456789012345678.0, TextOutputOptions()));
+  EXPECT_EQ("4.9406564584124654e-324",
+            WriteFloatToString(::std::numeric_limits<double>::denorm_min(),
+                               TextOutputOptions()));
+  EXPECT_EQ("1.7976931348623157e+308",
+            WriteFloatToString(::std::numeric_limits<double>::max(),
+                               TextOutputOptions()));
+
+  EXPECT_EQ("0", WriteFloatToString(0.0f, TextOutputOptions()));
+  EXPECT_EQ("1", WriteFloatToString(1.0f, TextOutputOptions()));
+  EXPECT_EQ("1.5", WriteFloatToString(1.5f, TextOutputOptions()));
+  EXPECT_EQ("1.60000002", WriteFloatToString(1.6f, TextOutputOptions()));
+  EXPECT_EQ("123456792", WriteFloatToString(123456789.0f, TextOutputOptions()));
+  EXPECT_EQ("1.23456784e+16",
+            WriteFloatToString(12345678901234567.0f, TextOutputOptions()));
+  EXPECT_EQ("-1.23456784e+16",
+            WriteFloatToString(-12345678901234567.0f, TextOutputOptions()));
+  EXPECT_EQ("-1.00000003e+16",
+            WriteFloatToString(-10000000000000000.0f, TextOutputOptions()));
+  EXPECT_EQ("1.40129846e-45",
+            WriteFloatToString(::std::numeric_limits<float>::denorm_min(),
+                               TextOutputOptions()));
+  EXPECT_EQ("3.40282347e+38",
+            WriteFloatToString(::std::numeric_limits<float>::max(),
+                               TextOutputOptions()));
+}
+
+TEST(WriteFloatToTextStream, Infinities) {
+  EXPECT_EQ("Inf", WriteFloatToString(2 * ::std::numeric_limits<double>::max(),
+                                      TextOutputOptions()));
+  EXPECT_EQ("Inf", WriteFloatToString(2 * ::std::numeric_limits<float>::max(),
+                                      TextOutputOptions()));
+  EXPECT_EQ("-Inf",
+            WriteFloatToString(-2 * ::std::numeric_limits<double>::max(),
+                               TextOutputOptions()));
+  EXPECT_EQ("-Inf", WriteFloatToString(-2 * ::std::numeric_limits<float>::max(),
+                                       TextOutputOptions()));
+}
+
+// C++ does not provide great low-level manipulation for NaNs, so we resort to
+// this mess.
+double MakeNanDouble(::std::uint64_t payload, int sign) {
+  payload |= 0x7ff0000000000000UL;
+  if (sign < 0) {
+    payload |= 0x8000000000000000UL;
+  }
+  double result;
+  ::std::memcpy(&result, &payload, sizeof result);
+  return result;
+}
+
+float MakeNanFloat(::std::uint32_t payload, int sign) {
+  payload |= 0x7f800000U;
+  if (sign < 0) {
+    payload |= 0x80000000U;
+  }
+  float result;
+  ::std::memcpy(&result, &payload, sizeof result);
+  return result;
+}
+
+TEST(WriteFloatToTextStream, Nans) {
+  EXPECT_EQ("NaN(0x1)",
+            WriteFloatToString(MakeNanDouble(1, 0), TextOutputOptions()));
+  EXPECT_EQ("NaN(0x1)",
+            WriteFloatToString(MakeNanFloat(1, 0), TextOutputOptions()));
+  EXPECT_EQ("NaN(0x10000)",
+            WriteFloatToString(MakeNanDouble(0x10000, 0), TextOutputOptions()));
+  EXPECT_EQ("NaN(0x7fffff)", WriteFloatToString(MakeNanFloat(0x7fffffU, 0),
+                                                TextOutputOptions()));
+  EXPECT_EQ("NaN(0xfffffffffffff)",
+            WriteFloatToString(MakeNanDouble(0xfffffffffffffUL, 0),
+                               TextOutputOptions()));
+  EXPECT_EQ("-NaN(0x7fffff)", WriteFloatToString(MakeNanFloat(0x7fffffU, -1),
+                                                 TextOutputOptions()));
+  EXPECT_EQ("-NaN(0xfffffffffffff)",
+            WriteFloatToString(MakeNanDouble(0xfffffffffffffUL, -1),
+                               TextOutputOptions()));
+  EXPECT_EQ("NaN(0x10000)",
+            WriteFloatToString(MakeNanFloat(0x10000, 0), TextOutputOptions()));
+  EXPECT_EQ("-NaN(0x1)",
+            WriteFloatToString(MakeNanDouble(1, -1), TextOutputOptions()));
+  EXPECT_EQ("-NaN(0x1)",
+            WriteFloatToString(MakeNanFloat(1, -1), TextOutputOptions()));
+  EXPECT_EQ("-NaN(0x10000)", WriteFloatToString(MakeNanDouble(0x10000, -1),
+                                                TextOutputOptions()));
+  EXPECT_EQ("-NaN(0x10000)",
+            WriteFloatToString(MakeNanFloat(0x10000, -1), TextOutputOptions()));
+  EXPECT_EQ("-NaN(0x1_0000)",
+            WriteFloatToString(MakeNanDouble(0x10000, -1),
+                               TextOutputOptions().WithDigitGrouping(true)));
+  EXPECT_EQ("-NaN(0x1_0000)",
+            WriteFloatToString(MakeNanFloat(0x10000, -1),
+                               TextOutputOptions().WithDigitGrouping(true)));
+}
+
+TEST(DecodeFloat, RegularNumbers) {
+  double double_result;
+  EXPECT_TRUE(DecodeFloat("0", &double_result));
+  EXPECT_EQ(0.0, double_result);
+  EXPECT_FALSE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("-0", &double_result));
+  EXPECT_EQ(0.0, double_result);
+  EXPECT_TRUE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("0.0", &double_result));
+  EXPECT_EQ(0.0, double_result);
+  EXPECT_TRUE(DecodeFloat("0.0e100", &double_result));
+  EXPECT_EQ(0.0, double_result);
+  EXPECT_TRUE(DecodeFloat("0x0.0p100", &double_result));
+  EXPECT_EQ(0.0, double_result);
+  EXPECT_TRUE(DecodeFloat("1", &double_result));
+  EXPECT_EQ(1.0, double_result);
+  EXPECT_TRUE(DecodeFloat("1.5", &double_result));
+  EXPECT_EQ(1.5, double_result);
+  EXPECT_TRUE(DecodeFloat("1.6", &double_result));
+  EXPECT_EQ(1.6, double_result);
+  EXPECT_TRUE(DecodeFloat("1.6000000000000001", &double_result));
+  EXPECT_EQ(1.6, double_result);
+  EXPECT_TRUE(DecodeFloat("123456789", &double_result));
+  EXPECT_EQ(123456789.0, double_result);
+  EXPECT_TRUE(DecodeFloat("-1.234567890123458e+17", &double_result));
+  EXPECT_EQ(-1.234567890123458e+17, double_result);
+  EXPECT_TRUE(DecodeFloat("4.9406564584124654e-324", &double_result));
+  EXPECT_EQ(4.9406564584124654e-324, double_result);
+  EXPECT_TRUE(DecodeFloat("1.7976931348623157e+308", &double_result));
+  EXPECT_EQ(1.7976931348623157e+308, double_result);
+  EXPECT_TRUE(DecodeFloat(
+      "000000000000000000000000000004.9406564584124654e-324", &double_result));
+  EXPECT_EQ(4.9406564584124654e-324, double_result);
+
+  float float_result;
+  EXPECT_TRUE(DecodeFloat("0", &float_result));
+  EXPECT_EQ(0.0f, float_result);
+  EXPECT_FALSE(::std::signbit(float_result));
+  EXPECT_TRUE(DecodeFloat("-0", &float_result));
+  EXPECT_EQ(0.0f, float_result);
+  EXPECT_TRUE(::std::signbit(float_result));
+  EXPECT_TRUE(DecodeFloat("0.0", &float_result));
+  EXPECT_EQ(0.0f, float_result);
+  EXPECT_TRUE(DecodeFloat("0.0e100", &float_result));
+  EXPECT_EQ(0.0f, float_result);
+  EXPECT_TRUE(DecodeFloat("0x0.0p100", &float_result));
+  EXPECT_EQ(0.0f, float_result);
+  EXPECT_TRUE(DecodeFloat("1", &float_result));
+  EXPECT_EQ(1.0f, float_result);
+  EXPECT_TRUE(DecodeFloat("1.5", &float_result));
+  EXPECT_EQ(1.5f, float_result);
+  EXPECT_TRUE(DecodeFloat("1.6", &float_result));
+  EXPECT_EQ(1.6f, float_result);
+  EXPECT_TRUE(DecodeFloat("1.6000000000000001", &float_result));
+  EXPECT_EQ(1.6f, float_result);
+  EXPECT_TRUE(DecodeFloat("123456789", &float_result));
+  EXPECT_EQ(123456789.0f, float_result);
+  EXPECT_TRUE(DecodeFloat("-1.23456784e+16", &float_result));
+  EXPECT_EQ(-1.23456784e+16f, float_result);
+  EXPECT_TRUE(DecodeFloat("1.40129846e-45", &float_result));
+  EXPECT_EQ(1.40129846e-45f, float_result);
+  EXPECT_TRUE(DecodeFloat("3.40282347e+38", &float_result));
+  EXPECT_EQ(3.40282347e+38f, float_result);
+
+  // TODO(bolms): "_"-grouped numbers, like "123_456.789", should probably be
+  // allowed.
+}
+
+TEST(DecodeFloat, BadValues) {
+  double result;
+  float float_result;
+
+  // No text is not a value.
+  EXPECT_FALSE(DecodeFloat("", &result));
+
+  // Trailing characters after "Inf" are not allowed.
+  EXPECT_FALSE(DecodeFloat("INF+", &result));
+  EXPECT_FALSE(DecodeFloat("Infinity", &result));
+
+  // Trailing characters after "NaN" are not allowed.
+  EXPECT_FALSE(DecodeFloat("NaN(", &result));
+  EXPECT_FALSE(DecodeFloat("NaN(0]", &result));
+  EXPECT_FALSE(DecodeFloat("NaNaNaNa", &result));
+
+  // Non-number NaN payloads are not allowed.
+  EXPECT_FALSE(DecodeFloat("NaN()", &result));
+  EXPECT_FALSE(DecodeFloat("NaN(x)", &result));
+  EXPECT_FALSE(DecodeFloat("NaN(0x)", &result));
+
+  // Negative NaN payloads are not allowed.
+  EXPECT_FALSE(DecodeFloat("NaN(-1)", &result));
+  EXPECT_FALSE(DecodeFloat("NaN(-0)", &result));
+
+  // NaN with zero payload is infinity, and is thus not allowed.
+  EXPECT_FALSE(DecodeFloat("NaN(0)", &result));
+  EXPECT_FALSE(DecodeFloat("-NaN(0)", &result));
+
+  // NaN double payloads must be no more than 52 bits.
+  EXPECT_FALSE(DecodeFloat("NaN(0x10_0000_0000_0000)", &result));
+  EXPECT_FALSE(DecodeFloat("NaN(0x8000_0000_0000_0000)", &result));
+  EXPECT_FALSE(DecodeFloat("NaN(0x1_0000_0000_0000_0000)", &result));
+
+  // NaN float payloads must be no more than 23 bits.
+  EXPECT_FALSE(DecodeFloat("NaN(0x80_0000)", &float_result));
+  EXPECT_FALSE(DecodeFloat("NaN(0x8000_0000)", &float_result));
+  EXPECT_FALSE(DecodeFloat("NaN(0x1_0000_0000)", &float_result));
+
+  // Trailing characters after regular values are not allowed.
+  EXPECT_FALSE(DecodeFloat("0x", &result));
+  EXPECT_FALSE(DecodeFloat("0e0a", &result));
+  EXPECT_FALSE(DecodeFloat("0b0", &result));
+  EXPECT_FALSE(DecodeFloat("0a", &result));
+  EXPECT_FALSE(DecodeFloat("1..", &result));
+
+  // Grouping characters like "," should not be allowed.
+  EXPECT_FALSE(DecodeFloat("123,456", &result));
+  EXPECT_FALSE(DecodeFloat("123'456", &result));
+}
+
+TEST(DecodeFloat, Infinities) {
+  double double_result;
+  EXPECT_TRUE(DecodeFloat("Inf", &double_result));
+  EXPECT_TRUE(::std::isinf(double_result));
+  EXPECT_FALSE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("-Inf", &double_result));
+  EXPECT_TRUE(::std::isinf(double_result));
+  EXPECT_TRUE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("+Inf", &double_result));
+  EXPECT_TRUE(::std::isinf(double_result));
+  EXPECT_FALSE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("iNF", &double_result));
+  EXPECT_TRUE(::std::isinf(double_result));
+  EXPECT_FALSE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("-iNF", &double_result));
+  EXPECT_TRUE(::std::isinf(double_result));
+  EXPECT_TRUE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("+iNF", &double_result));
+  EXPECT_TRUE(::std::isinf(double_result));
+  EXPECT_FALSE(::std::signbit(double_result));
+}
+
+// Helper functions for converting NaNs to bit patterns, so that the exact bit
+// pattern result can be tested.
+::std::uint64_t DoubleBitPattern(double n) {
+  ::std::uint64_t result;
+  memcpy(&result, &n, sizeof(result));
+  return result;
+}
+
+::std::uint32_t FloatBitPattern(float n) {
+  ::std::uint32_t result;
+  memcpy(&result, &n, sizeof(result));
+  return result;
+}
+
+TEST(DecodeFloat, Nans) {
+  double double_result;
+  EXPECT_TRUE(DecodeFloat("nan", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_FALSE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("-NAN", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_TRUE(::std::signbit(double_result));
+  EXPECT_TRUE(DecodeFloat("NaN(1)", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_EQ(0x7ff0000000000001UL, DoubleBitPattern(double_result));
+  EXPECT_TRUE(DecodeFloat("nAn(0x1000)", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_EQ(0x7ff0000000001000UL, DoubleBitPattern(double_result));
+  EXPECT_TRUE(DecodeFloat("NaN(0b11000011)", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_EQ(0x7ff00000000000c3UL, DoubleBitPattern(double_result));
+  EXPECT_TRUE(DecodeFloat("-NaN(0b11000011)", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_EQ(0xfff00000000000c3UL, DoubleBitPattern(double_result));
+  EXPECT_TRUE(DecodeFloat("+NaN(0b11000011)", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_EQ(0x7ff00000000000c3UL, DoubleBitPattern(double_result));
+  EXPECT_TRUE(DecodeFloat("NaN(0xf_ffff_ffff_ffff)", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_EQ(0x7fffffffffffffffUL, DoubleBitPattern(double_result));
+  EXPECT_TRUE(DecodeFloat("-NaN(0xf_ffff_ffff_ffff)", &double_result));
+  EXPECT_TRUE(::std::isnan(double_result));
+  EXPECT_EQ(0xffffffffffffffffUL, DoubleBitPattern(double_result));
+
+  float float_result;
+  EXPECT_TRUE(DecodeFloat("nan", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_FALSE(::std::signbit(float_result));
+  EXPECT_TRUE(DecodeFloat("-NAN", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_TRUE(::std::signbit(float_result));
+  EXPECT_TRUE(DecodeFloat("NaN(1)", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_EQ(0x7f800001U, FloatBitPattern(float_result));
+  EXPECT_TRUE(DecodeFloat("nAn(0x1000)", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_EQ(0x7f801000U, FloatBitPattern(float_result));
+  EXPECT_TRUE(DecodeFloat("NaN(0b11000011)", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_EQ(0x7f8000c3U, FloatBitPattern(float_result));
+  EXPECT_TRUE(DecodeFloat("-NaN(0b11000011)", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_EQ(0xff8000c3U, FloatBitPattern(float_result));
+  EXPECT_TRUE(DecodeFloat("+NaN(0b11000011)", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_EQ(0x7f8000c3U, FloatBitPattern(float_result));
+  EXPECT_TRUE(DecodeFloat("NaN(0x7f_ffff)", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_EQ(0x7fffffffU, FloatBitPattern(float_result));
+  EXPECT_TRUE(DecodeFloat("-NaN(0x7f_ffff)", &float_result));
+  EXPECT_TRUE(::std::isnan(float_result));
+  EXPECT_EQ(0xffffffffU, FloatBitPattern(float_result));
+}
+
+}  // namespace test
+}  // namespace support
+}  // namespace emboss
diff --git a/public/emboss_view_parameters.h b/public/emboss_view_parameters.h
new file mode 100644
index 0000000..f2a6e06
--- /dev/null
+++ b/public/emboss_view_parameters.h
@@ -0,0 +1,45 @@
+// 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.
+
+// Helper classes for constructing the `Parameters` template argument to view
+// classes.
+
+#ifndef EMBOSS_PUBLIC_EMBOSS_VIEW_PARAMETERS_H_
+#define EMBOSS_PUBLIC_EMBOSS_VIEW_PARAMETERS_H_
+
+namespace emboss {
+namespace support {
+
+template <int kBitsParam, typename Verifier>
+struct FixedSizeViewParameters {
+  static constexpr int kBits = kBitsParam;
+  template <typename ValueType>
+  static constexpr bool ValueIsOk(ValueType value) {
+    return Verifier::ValueIsOk(value);
+  }
+  // TODO(bolms): add AllValuesAreOk(), and use it to shortcut Ok() processing
+  // for arrays and other compound objects.
+};
+
+struct AllValuesAreOk {
+  template <typename ValueType>
+  static constexpr bool ValueIsOk(ValueType) {
+    return true;
+  }
+};
+
+}  // namespace support
+}  // namespace emboss
+
+#endif  // EMBOSS_PUBLIC_EMBOSS_VIEW_PARAMETERS_H_
diff --git a/public/ir_pb2.py b/public/ir_pb2.py
new file mode 100644
index 0000000..b33a06b
--- /dev/null
+++ b/public/ir_pb2.py
@@ -0,0 +1,937 @@
+# 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.
+
+# Intermediate representation (IR) for Emboss.
+#
+# This was originally a Google Protocol Buffer file, but as of 2019 it turns
+# out that a) the public Google Python Protocol Buffer implementation is
+# extremely slow, and b) all the ways of getting Bazel+Python+Protocol Buffers
+# to play nice are hacky and fragile.
+#
+# Thus, this file, which presents a similar-enough interface that the rest of
+# Emboss can use it with minimal changes.
+#
+# Protobufs have a really, really strange, un-Pythonic interface, with tricky
+# implicit semantics -- mostly around magically instantiating protos when you
+# assign to some deeply-nested field.  I (bolms@) would *strongly* prefer to
+# have a more explicit interface, but don't (currently) have time to refactor
+# everything that touches the IR (i.e., the entire compiler).
+
+import json
+import re
+import sys
+
+
+if sys.version_info[0] == 2:
+    _text = unicode
+    _text_types = (unicode, str)
+    _int = long
+    _int_types = (int, long)
+else:
+    _text = str
+    _text_types = (str,)
+    _int = int
+    _int_types = (int,)
+
+
+_BASIC_TYPES = _text_types + _int_types + (bool,)
+
+
+class Optional(object):
+    def __init__(self, type, oneof=None, names=None):
+        self._type = type
+        self._oneof = oneof
+        self._names = names
+
+    def __get__(self, obj, type=None):
+        result = obj.raw_fields.get(self.name, None)
+        if result is not None:
+            return result
+        if self.type in _BASIC_TYPES:
+            return self._type()
+        result = self._type()
+
+        def on_write():
+            self._set_value(obj, result)
+
+        result.set_on_write(on_write)
+        return result
+
+    def __set__(self, obj, value):
+        if issubclass(self._type, _BASIC_TYPES):
+            self.set(obj, value)
+        else:
+            raise AttributeError("Cannot set {} (type {}) for type {}".format(
+                value, value.__class__, self._type))
+
+    def _set_value(self, obj, value):
+        if self._oneof is not None:
+            current = obj.oneofs.get(self._oneof)
+            if current in obj.raw_fields:
+                del obj.raw_fields[current]
+            obj.oneofs[self._oneof] = self.name
+        obj.raw_fields[self.name] = value
+        obj.on_write()
+
+    def set(self, obj, value):
+        if value is None:
+            return
+        if isinstance(value, dict):
+            self._set_value(obj, self._type(**value))
+        elif isinstance(value, _text) and self._names:
+            self._set_value(obj, self._type(self._names(value)))
+        elif (not isinstance(value, self._type) and
+              not (self._type == _int and isinstance(value, _int_types)) and
+              not (self._type == _text and isinstance(value, _text_types))):
+            raise AttributeError("Cannot set {} (type {}) for type {}".format(
+                value, value.__class__, self._type))
+        elif issubclass(self._type, Message):
+            self._set_value(obj, self._type(**value.raw_fields))
+        else:
+            self._set_value(obj, self._type(value))
+
+    def resolve_type(self):
+        if isinstance(self._type, type(lambda: None)):
+            self._type = self._type()
+
+    @property
+    def type(self):
+        return self._type
+
+
+class Oneof(object):
+    def __init__(self, **members):
+        self.members = members
+
+    def __get__(self, obj, type=None):
+        return obj.oneofs[self.name]
+
+    def __set__(self, obj, value):
+        raise AttributeError("Cannot set {}".format(self.name))
+
+
+class TypedScopedList(object):
+    def __init__(self, type, on_write=lambda: None):
+        self._type = type
+        self._list = []
+        self._on_write = on_write
+
+    def __iter__(self):
+        return iter(self._list)
+
+    def __delitem__(self, key):
+        del self._list[key]
+
+    def __getitem__(self, key):
+        return self._list[key]
+
+    def extend(self, values):
+        for value in values:
+            if isinstance(value, dict):
+                self._list.append(self._type(**value))
+            elif (not isinstance(value, self._type) and
+                  not (self._type == _int and isinstance(value, _int_types)) and
+                  not (self._type == _text and isinstance(value, _text_types))):
+                raise TypeError(
+                        "Needed {}, got {} ({!r})".format(
+                            self._type, value.__class__, value))
+            else:
+                if self._type in _BASIC_TYPES:
+                    self._list.append(self._type(value))
+                else:
+                    self._list.append(self._type(**value.raw_fields))
+        self._on_write()
+
+    def __repr__(self):
+        return repr(self._list)
+
+    def __len__(self):
+        return len(self._list)
+
+    def __eq__(self, other):
+      return ((self.__class__ == other.__class__ and
+               self._list == other._list) or
+              (isinstance(other, list) and self._list == other))
+
+    def __ne__(self, other):
+      return not (self == other)
+
+
+class Repeated(object):
+    def __init__(self, type):
+        self._type = type
+
+    def __get__(self, obj, type=None):
+        return obj.raw_fields[self.name]
+
+    def __set__(self, obj, value):
+        raise AttributeError("Cannot set {}".format(self.name))
+
+    def set(self, obj, values):
+        typed_list = obj.raw_fields[self.name]
+        if not isinstance(values, (list, TypedScopedList)):
+            raise TypeError("Cannot initialize repeated field {} from {}".format(
+                self.name, values.__class__))
+        del typed_list[:]
+        typed_list.extend(values)
+
+    def resolve_type(self):
+        if isinstance(self._type, type(lambda: None)):
+            self._type = self._type()
+
+    @property
+    def type(self):
+        return self._type
+
+
+_deferred_specs = []
+
+def message(cls):
+    # TODO(bolms): move this into __init_subclass__ after dropping Python 2
+    # support.
+    _deferred_specs.append(cls)
+    return cls
+
+class Message(object):
+
+    def __init__(self, **field_values):
+        self.oneofs = {}
+        self._on_write = lambda: None
+        self._initialize_raw_fields_from(field_values)
+
+    def _initialize_raw_fields_from(self, field_values):
+        self.raw_fields = {}
+        for name, type in self.repeated_fields.items():
+            self.raw_fields[name] = TypedScopedList(type, self.on_write)
+        for k, v in field_values.items():
+            spec = self.field_specs.get(k)
+            if spec is None:
+                raise AttributeError("No field {} on {}.".format(
+                    k, self.__class__.__name__))
+            spec.set(self, v)
+
+    @classmethod
+    def from_json(cls, text):
+        as_dict = json.loads(text)
+        return cls(**as_dict)
+
+    def on_write(self):
+        self._on_write()
+        self._on_write = lambda: None
+
+    def set_on_write(self, on_write):
+        self._on_write = on_write
+
+    def __eq__(self, other):
+        return (self.__class__ == other.__class__ and
+                self.raw_fields == other.raw_fields)
+
+    def CopyFrom(self, other):
+        if self.__class__ != other.__class__:
+            raise TypeError("{} cannot CopyFrom {}".format(
+                self.__class__.__name__, other.__class__.__name__))
+        self._initialize_raw_fields_from(other.raw_fields)
+        self.on_write()
+
+    def HasField(self, name):
+        return name in self.raw_fields
+
+    def WhichOneof(self, oneof_name):
+        return self.oneofs.get(oneof_name)
+
+    def to_dict(self):
+        result = {}
+        for k, v in self.raw_fields.items():
+          if isinstance(v, _BASIC_TYPES):
+              result[k] = v
+          elif isinstance(v, TypedScopedList):
+              if len(v):
+                  # For compatibility with the proto world, empty lists are just
+                  # elided.
+                  result[k] = [
+                      item if isinstance(item, _BASIC_TYPES) else item.to_dict()
+                      for item in v
+                  ]
+          else:
+              result[k] = v.to_dict()
+        return result
+
+    def __repr__(self):
+        return self.to_json(separators=(',', ':'), sort_keys=True)
+
+    def to_json(self, *args, **kwargs):
+        return json.dumps(self.to_dict(), *args, **kwargs)
+
+    def __str__(self):
+        return _text(self.to_dict())
+
+
+def _initialize_deferred_specs():
+    for cls in _deferred_specs:
+        field_specs = {}
+        repeated_fields = {}
+        for k, v in cls.__dict__.items():
+            if k[0] == "_":
+                continue
+            if isinstance(v, (Optional, Repeated)):
+                v.name = k
+                v.resolve_type()
+                field_specs[k] = v
+                if isinstance(v, Repeated):
+                  repeated_fields[k] = v.type
+        cls.field_specs = field_specs
+        cls.repeated_fields = repeated_fields
+
+
+################################################################################
+
+
+@message
+class Position(Message):
+    """A zero-width position within a source file."""
+    line = Optional(int)    # Line (starts from 1).
+    column = Optional(int)  # Column (starts from 1).
+
+
+@message
+class Location(Message):
+    """A half-open start:end range within a source file."""
+    start = Optional(Position)  # Beginning of the range.
+    end = Optional(Position)    # One column past the end of the range.
+
+    # True if this Location is outside of the parent object's Location.
+    is_disjoint_from_parent = Optional(bool)
+
+    # True if this Location's parent was synthesized, and does not directly
+    # appear in the source file.  The Emboss front end uses this field to cull
+    # irrelevant error messages.
+    is_synthetic = Optional(bool)
+
+
+@message
+class Word(Message):
+    """IR for a bare word in the source file.
+
+    This is used in NameDefinitions and References."""
+
+    text = Optional(_text)
+    source_location = Optional(Location)
+
+
+@message
+class String(Message):
+    """IR for a string in the source file."""
+    text = Optional(_text)
+    source_location = Optional(Location)
+
+
+@message
+class Documentation(Message):
+    text = Optional(_text)
+    source_location = Optional(Location)
+
+
+@message
+class BooleanConstant(Message):
+    """IR for a boolean constant."""
+    value = Optional(bool)
+    source_location = Optional(Location)
+
+
+@message
+class Empty(Message):
+    """Placeholder message for automatic element counts for arrays."""
+    source_location = Optional(Location)
+
+
+@message
+class NumericConstant(Message):
+    """IR for any numeric constant."""
+
+    # Numeric constants are stored as decimal strings; this is the simplest way
+    # to store the full -2**63..+2**64 range.
+    #
+    # TODO(bolms): switch back to int, and just use strings during
+    # serialization, now that we're free of proto.
+    value = Optional(_text)
+    source_location = Optional(Location)
+
+
+@message
+class Function(Message):
+    """IR for a single function (+, -, *, ==, $max, etc.) in an expression."""
+    UNKNOWN = 0
+    ADDITION = 1           # +
+    SUBTRACTION = 2        # -
+    MULTIPLICATION = 3     # *
+    EQUALITY = 4           # ==
+    INEQUALITY = 5         # !=
+    AND = 6                # &&
+    OR = 7                 # ||
+    LESS = 8               # <
+    LESS_OR_EQUAL = 9      # <=
+    GREATER = 10           # >
+    GREATER_OR_EQUAL = 11  # >=
+    CHOICE = 12            # ?:
+    MAXIMUM = 13           # $max()
+    PRESENCE = 14          # $present()
+    UPPER_BOUND = 15       # $upper_bound()
+    LOWER_BOUND = 16       # $lower_bound()
+
+    function = Optional(int, names=lambda x: getattr(Function, x))
+    args = Repeated(lambda: Expression)
+    function_name = Optional(Word)
+    source_location = Optional(Location)
+
+
+@message
+class CanonicalName(Message):
+    """CanonicalName is the unique, absolute name for some object.
+
+    A CanonicalName is the unique, absolute name for some object (Type, field,
+    etc.) in the IR.  It is used both in the definitions of objects ("struct
+    Foo"), and in references to objects (a field of type "Foo")."""
+
+    # The module_file is the Module.source_file_name of the Module in which this
+    # object's definition appears.  Note that the Prelude always has a
+    # Module.source_file_name of "", and thus references to Prelude names will
+    # have module_file == "".
+    module_file = Optional(_text)
+
+    # The object_path is the canonical path to the object definition within its
+    # module file.  For example, the field "bar" would have an object path of
+    # ["Foo", "bar"]:
+    #
+    # struct Foo:
+    #   0:3  UInt  bar
+    #
+    #
+    # The enumerated name "BOB" would have an object path of ["Baz", "Qux",
+    # "BOB"]:
+    #
+    # struct Baz:
+    #   0:3  Qux   qux
+    #
+    #   enum Qux:
+    #     BOB = 0
+    object_path = Repeated(_text)
+
+
+@message
+class NameDefinition(Message):
+    """NameDefinition is IR for the name of an object, within the object.
+
+    That is, a TypeDefinition or Field will hold a NameDefinition as its
+    name."""
+
+    # The name, as directly generated from the source text.  name.text will
+    # match the last element of canonical_name.object_path.  Note that in some
+    # cases, the exact string in name.text may not appear in the source text.
+    name = Optional(Word)
+
+    # The CanonicalName that will appear in References.  This field is
+    # technically redundant: canonical_name.module_file should always match the
+    # source_file_name of the enclosing Module, and canonical_name.object_path
+    # should always match the names of parent nodes.
+    canonical_name = Optional(CanonicalName)
+
+    # If true, indicates that this is an automatically-generated name, which
+    # should not be visible outside of its immediate namespace.
+    is_anonymous = Optional(bool)
+
+    # The location of this NameDefinition in source code.
+    source_location = Optional(Location)
+
+
+@message
+class Reference(Message):
+    """A Reference holds the canonical name of something defined elsewhere.
+
+    For example, take this fragment:
+
+      struct Foo:
+        0:3  UInt    size (s)
+        4:s  Int:8[] payload
+
+    "Foo", "size", and "payload" will become NameDefinitions in their
+    corresponding Field and Message IR objects, while "UInt", the second "s",
+    and "Int" are References.  Note that the second "s" will have a
+    canonical_name.object_path of ["Foo", "size"], not ["Foo", "s"]: the
+    Reference always holds the single "true" name of the object, regardless of
+    what appears in the .emb."""
+
+    # The canonical name of the object being referred to.  This name should be
+    # used to find the object in the IR.
+    canonical_name = Optional(CanonicalName)
+
+    # The source_name is the name the user entered in the source file; it could
+    # be either relative or absolute, and may be an alias (and thus not match
+    # any part of the canonical_name).  Back ends should use canonical_name for
+    # name lookup, and reserve source_name for error messages.
+    source_name = Repeated(Word)
+
+    # If true, then symbol resolution should only look at local names when
+    # resolving source_name.  This is used so that the names of inline types
+    # aren't "ambiguous" if there happens to be another type with the same name
+    # at a parent scope.
+    is_local_name = Optional(bool)
+
+    # TODO(bolms): Allow absolute paths starting with ".".
+
+    # Note that this is the source_location of the *Reference*, not of the
+    # object to which it refers.
+    source_location = Optional(Location)
+
+
+@message
+class FieldReference(Message):
+    """IR for a "field" or "field.sub.subsub" reference in an expression.
+
+    The first element of "path" is the "base" field, which should be directly
+    readable in the (runtime) context of the expression.  For example:
+    
+        struct Foo:
+          0:1  UInt      header_size (h)
+          0:h  UInt:8[]  header_bytes
+    
+    The "h" will translate to ["Foo", "header_size"], which will be the first
+    (and in this case only) element of "path".
+    
+    Subsequent path elements should be treated as subfields.  For example, in:
+    
+        struct Foo:
+          struct Sizes:
+            0:1  UInt  header_size
+            1:2  UInt  body_size
+          0                 [+2]                  Sizes     sizes
+          0                 [+sizes.header_size]  UInt:8[]  header
+          sizes.header_size [+sizes.body_size]    UInt:8[]  body
+    
+    The references to "sizes.header_size" will have a path of [["Foo",
+    "sizes"], ["Foo", "Sizes", "header_size"]].  Note that each path element is
+    a fully-qualified reference; some back ends (C++, Python) may only use the
+    last element, while others (C) may use the complete path.
+
+    This representation is a bit awkward, and is fundamentally limited to a
+    dotted list of static field names.  It does not allow an expression like
+    `array[n]` on the left side of a `.`.  At this point, it is an artifact of
+    the era during which I (bolms@) thought I could get away with skipping
+    compiler-y things."""
+
+    # TODO(bolms): Add composite types to the expression type system, and
+    # replace FieldReference with a "member access" Expression kind.  Further,
+    # move the symbol resolution for FieldReferences that is currently in
+    # symbol_resolver.py into type_check.py.
+
+    # TODO(bolms): Make the above change before declaring the IR to be "stable".
+
+    path = Repeated(Reference)
+    source_location = Optional(Location)
+
+
+@message
+class OpaqueType(Message):
+    pass
+
+
+@message
+class IntegerType(Message):
+    """Type of an integer expression."""
+
+    # For optimization, the modular congruence of an integer expression is
+    # tracked.  This consists of a modulus and a modular_value, such that for
+    # all possible values of expression, expression MOD modulus ==
+    # modular_value.
+    #
+    # The modulus may be the special value "infinity" to indicate that the
+    # expression's value is exactly modular_value; otherwise, it should be a
+    # positive integer.
+    #
+    # A modulus of 1 places no constraints on the value.
+    #
+    # The modular_value should always be a nonnegative integer that is smaller
+    # than the modulus.
+    #
+    # Note that this is specifically the *modulus*, which is not equivalent to
+    # the value from C's '%' operator when the dividend is negative: in C, -7 %
+    # 4 == -3, but the modular_value here would be 1.  Python uses modulus: in
+    # Python, -7 % 4 == 1.
+    modulus = Optional(_text)
+    modular_value = Optional(_text)
+
+    # The minimum and maximum values of an integer are tracked and checked so
+    # that Emboss can implement reliable arithmetic with no operations
+    # overflowing either 64-bit unsigned or 64-bit signed 2's-complement
+    # integers.
+    #
+    # Note that constant subexpressions are allowed to overflow, as long as the
+    # final, computed constant value of the subexpression fits in a 64-bit
+    # value.
+    #
+    # The minimum_value may take the value "-infinity", and the maximum_value
+    # may take the value "infinity".  These sentinel values indicate that
+    # Emboss has no bound information for the Expression, and therefore the
+    # Expression may only be evaluated during compilation; the back end should
+    # never need to compile such an expression into the target language (e.g.,
+    # C++).
+    minimum_value = Optional(_text)
+    maximum_value = Optional(_text)
+
+
+@message
+class BooleanType(Message):
+    value = Optional(bool)
+
+
+@message
+class EnumType(Message):
+    name = Optional(Reference)
+    value = Optional(_text)
+
+
+@message
+class ExpressionType(Message):
+    opaque = Optional(OpaqueType, "type")
+    integer = Optional(IntegerType, "type")
+    boolean = Optional(BooleanType, "type")
+    enumeration = Optional(EnumType, "type")
+
+
+@message
+class Expression(Message):
+    """ IR for an expression.
+
+    An Expression is a potentially-recursive data structure.  It can either
+    represent a leaf node (constant or reference) or an operation combining
+    other Expressions (function)."""
+
+    constant = Optional(NumericConstant, "expression")
+    constant_reference = Optional(Reference, "expression")
+    function = Optional(Function, "expression")
+    field_reference = Optional(FieldReference, "expression")
+    boolean_constant = Optional(BooleanConstant, "expression")
+    builtin_reference = Optional(Reference, "expression")
+
+    type = Optional(ExpressionType)
+    source_location = Optional(Location)
+
+
+@message
+class ArrayType(Message):
+    """IR for an array type ("Int:8[12]" or "Message[2]" or "UInt[3][2]")."""
+    base_type = Optional(lambda: Type)
+
+    element_count = Optional(Expression, "size")
+    automatic = Optional(Empty, "size")
+
+    source_location = Optional(Location)
+
+
+@message
+class AtomicType(Message):
+    """IR for a non-array type ("UInt" or "Foo(Version.SIX)")."""
+    reference = Optional(Reference)
+    runtime_parameter = Repeated(Expression)
+    source_location = Optional(Location)
+
+
+@message
+class Type(Message):
+    """IR for a type reference ("UInt", "Int:8[12]", etc.)."""
+    atomic_type = Optional(AtomicType, "type")
+    array_type = Optional(ArrayType, "type")
+
+    size_in_bits = Optional(Expression)
+    source_location = Optional(Location)
+
+
+@message
+class AttributeValue(Message):
+    """IR for a attribute value."""
+    # TODO(bolms): Make String a type of Expression, and replace
+    # AttributeValue with Expression.
+    expression = Optional(Expression, "value")
+    string_constant = Optional(String, "value")
+
+    source_location = Optional(Location)
+
+
+@message
+class Attribute(Message):
+    """IR for a [name = value] attribute."""
+    name = Optional(Word)
+    value = Optional(AttributeValue)
+    back_end = Optional(Word)
+    is_default = Optional(bool)
+    source_location = Optional(Location)
+
+
+@message
+class WriteTransform(Message):
+    """IR which defines an expression-based virtual field write scheme.
+    
+    E.g., for a virtual field like `x_plus_one`:
+   
+        struct Foo:
+          0 [+1]  UInt  x
+          let x_plus_one = x + 1
+   
+    ... the `WriteMethod` would be `transform`, with `$logical_value - 1` for
+    `function_body` and `x` for `destination`."""
+
+    function_body = Optional(Expression)
+    destination = Optional(FieldReference)
+
+
+@message
+class WriteMethod(Message):
+    """IR which defines the method used for writing to a virtual field."""
+
+    # A physical Field can be written directly.
+    physical = Optional(bool, "method")
+
+    # A read_only Field cannot be written.
+    read_only = Optional(bool, "method")
+
+    # An alias is a direct, untransformed forward of another field; it can be
+    # implemented by directly returning a reference to the aliased field.
+    #
+    # Aliases are the only kind of virtual field that may have an opaque type.
+    alias = Optional(FieldReference, "method")
+
+    # A transform is a way of turning a logical value into a value which should
+    # be written to another field: A virtual field like `let y = x + 1` would
+    # have a transform WriteMethod to subtract 1 from the new `y` value, and
+    # write that to `x`.
+    transform = Optional(WriteTransform, "method")
+
+
+@message
+class FieldLocation(Message):
+    """IR for a field location."""
+    start = Optional(Expression)
+    size = Optional(Expression)
+    source_location = Optional(Location)
+
+
+@message
+class Field(Message):
+    """IR for a field in a struct definition.
+
+    There are two kinds of Field: physical fields have location and (physical)
+    type; virtual fields have read_transform.  Although there are differences,
+    in many situations physical and virtual fields are treated the same way,
+    and they can be freely intermingled in the source file."""
+    location = Optional(FieldLocation)  # The physical location of the field.
+    type = Optional(Type)               # The physical type of the field.
+
+    read_transform = Optional(Expression)  # The value of a virtual field.
+
+    # How this virtual field should be written.
+    write_method = Optional(WriteMethod)
+
+    name = Optional(NameDefinition)  # The name of the field.
+    abbreviation = Optional(Word)  # An optional short name for the field, only
+                                   # visible inside the enclosing bits/struct.
+    attribute = Repeated(Attribute)          # Field-specific attributes.
+    documentation = Repeated(Documentation)  # Field-specific documentation.
+
+    # The field only exists when existence_condition evaluates to true.  For
+    # example:
+    #
+    # struct Message:
+    #   0 [+4]  UInt         length
+    #   4 [+8]  MessageType  message_type
+    #   if message_type == MessageType.FOO:
+    #     8 [+length]  Foo   foo
+    #   if message_type == MessageType.BAR:
+    #     8 [+length]  Bar   bar
+    #   8+length [+4]  UInt  crc
+    #
+    # For length, message_type, and crc, existence_condition will be
+    # "boolean_constant { value: true }"
+    #
+    # For "foo", existence_condition will be:
+    #     function { function: EQUALITY
+    #                args: [reference to message_type]
+    #                args: { [reference to MessageType.FOO] } }
+    #
+    # The "bar" field will have a similar existence_condition to "foo":
+    #     function { function: EQUALITY
+    #                args: [reference to message_type]
+    #                args: { [reference to MessageType.BAR] } }
+    #
+    # When message_type is MessageType.BAR, the Message struct does not contain
+    # field "foo", and vice versa for message_type == MessageType.FOO and field
+    # "bar": those fields only conditionally exist in the structure.
+    #
+    # TODO(bolms): Document conditional fields better, and replace some of this
+    # explanation with a reference to the documentation.
+    existence_condition = Optional(Expression)
+    source_location = Optional(Location)
+
+
+@message
+class Structure(Message):
+    """IR for a bits or struct definition."""
+    field = Repeated(Field)
+
+    # The fields in `field` are listed in the order they appear in the original
+    # .emb.
+    #
+    # For text format output, this can lead to poor results.  Take the following
+    # struct:
+    #
+    #     struct Foo:
+    #       b [+4]  UInt  a
+    #       0 [+4]  UInt  b
+    #
+    # Here, the location of `a` depends on the current value of `b`.  Because of
+    # this, if someone calls
+    #
+    #     emboss::UpdateFromText(foo_view, "{ a: 10, b: 4 }");
+    #
+    # then foo_view will not be updated the way one would expect: if `b`'s value
+    # was something other than 4 to start with, then `UpdateFromText` will write
+    # the 10 to some other location, then update `b` to 4.
+    #
+    # To avoid surprises, `emboss::DumpAsText` should return `"{ b: 4, a: 10
+    # }"`.
+    #
+    # The `fields_in_dependency_order` field provides a permutation of `field`
+    # such that each field appears after all of its dependencies.  For example,
+    # `struct Foo`, above, would have `{ 1, 0 }` in
+    # `fields_in_dependency_order`.
+    #
+    # The exact ordering of `fields_in_dependency_order` is not guaranteed, but
+    # some effort is made to keep the order close to the order fields are listed
+    # in the original `.emb` file.  In particular, if the ordering 0, 1, 2, 3,
+    # ... satisfies dependency ordering, then `fields_in_dependency_order` will
+    # be `{ 0, 1, 2, 3, ... }`.
+    fields_in_dependency_order = Repeated(int)
+
+    source_location = Optional(Location)
+
+
+@message
+class External(Message):
+    """IR for an external type declaration."""
+    # Externals have no values other than name and attribute list, which are
+    # common to all type definitions.
+
+    source_location = Optional(Location)
+
+
+@message
+class EnumValue(Message):
+    """IR for a single value within an enumerated type."""
+    name = Optional(NameDefinition)          # The name of the enum value.
+    value = Optional(Expression)             # The value of the enum value.
+    documentation = Repeated(Documentation)  # Value-specific documentation.
+
+    source_location = Optional(Location)
+
+
+@message
+class Enum(Message):
+    """IR for an enumerated type definition."""
+    value = Repeated(EnumValue)
+    source_location = Optional(Location)
+
+
+@message
+class Import(Message):
+    """IR for an import statement in a module."""
+    file_name = Optional(String)  # The file to import.
+    local_name = Optional(Word)   # The name to use within this module.
+    source_location = Optional(Location)
+
+
+@message
+class RuntimeParameter(Message):
+    name = Optional(NameDefinition)  # The name of the parameter.
+    type = Optional(ExpressionType)  # The type of the parameter.
+
+    # For convenience and readability, physical types may be used in the .emb
+    # source instead of a full expression type.  That way, users can write
+    # something like:
+    #
+    #     struct Foo(version :: UInt:8):
+    #
+    # instead of:
+    #
+    #     struct Foo(version :: {$int x |: 0 <= x <= 255}):
+    #
+    # In these cases, physical_type_alias holds the user-supplied type, and type
+    # is filled in after initial parsing is finished.
+    #
+    # TODO(bolms): Actually implement the set builder type notation.
+    physical_type_alias = Optional(Type)
+
+    source_location = Optional(Location)
+
+
+@message
+class TypeDefinition(Message):
+    """Container IR for a type definition (struct, union, etc.)"""
+
+    # The "addressable unit" is the size of the smallest unit that can be read
+    # from the backing store that this type expects.  For `struct`s, this is
+    # BYTE; for `enum`s and `bits`, this is BIT, and for `external`s it depends
+    # on the specific type.
+
+    NONE = 0
+    BIT = 1
+    BYTE = 8
+
+    external = Optional(External, "type")
+    enumeration = Optional(Enum, "type")
+    structure = Optional(Structure, "type")
+
+    name = Optional(NameDefinition)  # The name of the type.
+    attribute = Repeated(Attribute)  # All attributes attached to the type.
+    documentation = Repeated(Documentation)     # Docs for the type.
+    subtype = Repeated(lambda: TypeDefinition)  # Subtypes of this type.
+    addressable_unit = Optional(
+        int, names=lambda x: getattr(TypeDefinition, x))
+
+    # If the type requires parameters at runtime, these are its parameters.
+    # These are currently only allowed on structures, but in the future they
+    # should be allowed on externals.
+    runtime_parameter = Repeated(RuntimeParameter)
+
+    source_location = Optional(Location)
+
+
+@message
+class Module(Message):
+    """The IR for an individual Emboss module (file)."""
+    attribute = Repeated(Attribute)          # Module-level attributes.
+    type = Repeated(TypeDefinition)          # Module-level type definitions.
+    documentation = Repeated(Documentation)  # Module-level docs.
+    foreign_import = Repeated(Import)        # Other modules imported.
+    source_location = Optional(Location)    # Source code covered by this IR.
+    source_file_name = Optional(_text)    # Name of the source file.
+
+
+@message
+class EmbossIr(Message):
+    """The top-level IR for an Emboss module and all of its dependencies."""
+    # All modules.  The first entry will be the main module; back ends should
+    # generate code corresponding to that module.  The second entry will be the
+    # prelude module.
+    module = Repeated(Module)
+
+
+_initialize_deferred_specs()