pw_tokenizer: Replace string literals with tokens

pw_tokenizer provides macros that replace printf-style string literals
with 32-bit hashes at compile time. The string literals are removed
from the resulting binary, which dramatically reduces the binary size.
Like any printf-style string, binary versions of the strings can be
formatted with arguments and then transmitted or stored.

The pw_tokenizer module is general purpose, but its most common use case
is binary logging. In binary logging, human-readable text logs are
replaced with binary tokens. These are decoded off-device.

This commit includes the C and C++ code for tokenizing strings. It also
includes a C++ library for decoding tokenized strings.

Change-Id: I6d5737ab2d6dfdd76dcf70c852b547fdcd68d683
diff --git a/BUILD.gn b/BUILD.gn
index c30696b..b7acdea 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -55,6 +55,7 @@
     "$dir_pw_span",
     "$dir_pw_status",
     "$dir_pw_string",
+    "$dir_pw_tokenizer",
     "$dir_pw_unit_test",
     "$dir_pw_varint",
   ]
@@ -69,6 +70,7 @@
     "$dir_pw_span:tests",
     "$dir_pw_status:tests",
     "$dir_pw_string:tests",
+    "$dir_pw_tokenizer:tests",
     "$dir_pw_unit_test:tests",
     "$dir_pw_varint:tests",
   ]
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index d4c3767..050b326 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -57,5 +57,6 @@
     "$dir_pw_status:docs",
     "$dir_pw_string:docs",
     "$dir_pw_target_runner:docs",
+    "$dir_pw_tokenizer:docs",
   ]
 }
diff --git a/modules.gni b/modules.gni
index 59faf73..fbbee69 100644
--- a/modules.gni
+++ b/modules.gni
@@ -36,6 +36,7 @@
 dir_pw_status = "$dir_pigweed/pw_status"
 dir_pw_string = "$dir_pigweed/pw_string"
 dir_pw_target_runner = "$dir_pigweed/pw_target_runner"
+dir_pw_tokenizer = "$dir_pigweed/pw_tokenizer"
 dir_pw_toolchain = "$dir_pigweed/pw_toolchain"
 dir_pw_unit_test = "$dir_pigweed/pw_unit_test"
 dir_pw_varint = "$dir_pigweed/pw_varint"
diff --git a/pw_tokenizer/BUILD b/pw_tokenizer/BUILD
new file mode 100644
index 0000000..59883fc
--- /dev/null
+++ b/pw_tokenizer/BUILD
@@ -0,0 +1,162 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_tokenizer",
+    srcs = [
+        "public/pw_tokenizer/config.h",
+        "public/pw_tokenizer/internal/argument_types.h",
+        "public/pw_tokenizer/internal/argument_types_macro_4_byte.h",
+        "public/pw_tokenizer/internal/argument_types_macro_8_byte.h",
+        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h",
+        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h",
+        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h",
+        "public/pw_tokenizer/internal/tokenize_string.h",
+        "tokenize.cc",
+    ],
+    hdrs = [
+        "public/pw_tokenizer/pw_tokenizer_65599_fixed_length_hash.h",
+        "public/pw_tokenizer/tokenize.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_span",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_library(
+    name = "decoder",
+    srcs = [
+        "decode.cc",
+        "detokenize.cc",
+        "token_database.cc",
+    ],
+    hdrs = [
+        "public/pw_tokenizer/detokenize.h",
+        "public/pw_tokenizer/internal/decode.h",
+        "public/pw_tokenizer/token_database.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_span",
+        "//pw_varint",
+    ],
+)
+
+# Executable for generating test data for the C++ and Python detokenizers. This
+# target should only be built for the host.
+pw_cc_binary(
+    name = "generate_decoding_test_data",
+    srcs = [
+        "generate_decoding_test_data.cc",
+    ],
+    deps = [
+        ":decoder",
+        ":pw_tokenizer",
+        "//pw_preprocessor",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_test(
+    name = "argument_types_test",
+    srcs = [
+        "argument_types_test.c",
+        "argument_types_test.cc",
+        "pw_tokenizer_private/argument_types_test.h",
+    ],
+    deps = [
+        ":pw_tokenizer",
+    ],
+)
+
+pw_cc_test(
+    name = "decode_test",
+    srcs = [
+        "decode_test.cc",
+        "pw_tokenizer_private/tokenized_string_decoding_test_data.h",
+        "pw_tokenizer_private/varint_decoding_test_data.h",
+    ],
+    deps = [
+        ":decoder",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_test(
+    name = "detokenize_test",
+    srcs = [
+        "detokenize_test.cc",
+    ],
+    deps = [
+        ":decoder",
+    ],
+)
+
+pw_cc_test(
+    name = "hash_test",
+    srcs = [
+        "hash_test.cc",
+        "pw_tokenizer_private/generated_hash_test_cases.h",
+    ],
+    deps = [
+        ":pw_tokenizer",
+    ],
+)
+
+pw_cc_test(
+    name = "token_database_test",
+    srcs = [
+        "token_database_test.cc",
+    ],
+    deps = [
+        ":decoder",
+    ],
+)
+
+pw_cc_test(
+    name = "tokenize_test",
+    srcs = [
+        "pw_tokenizer_private/tokenize_test.h",
+        "tokenize_test.c",
+        "tokenize_test.cc",
+    ],
+    deps = [
+        ":pw_tokenizer",
+        "//pw_varint",
+    ],
+)
+
+# Create a shared library for the tokenizer JNI wrapper. The include paths for
+# the JNI headers must be available in the system or provided with the
+# pw_java_native_interface_include_dirs variable.
+filegroup(
+    name = "detokenizer_jni",
+    srcs = [
+        "java/dev/pigweed/tokenizer/detokenizer.cc",
+    ],
+)
diff --git a/pw_tokenizer/BUILD.gn b/pw_tokenizer/BUILD.gn
new file mode 100644
index 0000000..5567a7a
--- /dev/null
+++ b/pw_tokenizer/BUILD.gn
@@ -0,0 +1,213 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     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.
+
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+source_set("pw_tokenizer") {
+  public_configs = [
+    "$dir_pw_build:pw_default_cpp",
+    ":default_config",
+  ]
+  public_deps = [
+    "$dir_pw_preprocessor",
+    "$dir_pw_span",
+  ]
+  deps = [
+    "$dir_pw_varint",
+  ]
+  public = [
+    "public/pw_tokenizer/pw_tokenizer_65599_fixed_length_hash.h",
+    "public/pw_tokenizer/tokenize.h",
+  ]
+  sources = [
+    "public/pw_tokenizer/config.h",
+    "public/pw_tokenizer/internal/argument_types.h",
+    "public/pw_tokenizer/internal/argument_types_macro_4_byte.h",
+    "public/pw_tokenizer/internal/argument_types_macro_8_byte.h",
+    "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h",
+    "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h",
+    "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h",
+    "public/pw_tokenizer/internal/tokenize_string.h",
+    "tokenize.cc",
+  ]
+  sources += public
+  friend = [
+    ":argument_types_test",
+    ":hash_test",
+  ]
+}
+
+source_set("decoder") {
+  public_configs = [
+    "$dir_pw_build:pw_default_cpp",
+    ":default_config",
+  ]
+  public_deps = [
+    "$dir_pw_span",
+  ]
+  deps = [
+    "$dir_pw_varint",
+  ]
+  public = [
+    "public/pw_tokenizer/detokenize.h",
+    "public/pw_tokenizer/token_database.h",
+  ]
+  sources = [
+    "decode.cc",
+    "detokenize.cc",
+    "public/pw_tokenizer/internal/decode.h",
+    "token_database.cc",
+  ]
+  sources += public
+  friend = [
+    ":decode_test",
+    ":generate_decoding_test_data",
+  ]
+}
+
+# Executable for generating test data for the C++ and Python detokenizers. This
+# target should only be built for the host.
+executable("generate_decoding_test_data") {
+  deps = [
+    ":decoder",
+    ":pw_tokenizer",
+    "$dir_pw_varint",
+  ]
+  sources = [
+    "generate_decoding_test_data.cc",
+  ]
+}
+
+pw_test_group("tests") {
+  tests = [
+    ":argument_types_test",
+    ":decode_test",
+    ":detokenize_test",
+    ":hash_test",
+    ":token_database_test",
+    ":tokenize_test",
+  ]
+  group_deps = [
+    "$dir_pw_preprocessor:tests",
+    "$dir_pw_span:tests",
+    "$dir_pw_status:tests",
+  ]
+}
+
+pw_test("argument_types_test") {
+  sources = [
+    "argument_types_test.c",
+    "argument_types_test.cc",
+    "pw_tokenizer_private/argument_types_test.h",
+  ]
+  deps = [
+    ":pw_tokenizer",
+  ]
+}
+
+pw_test("decode_test") {
+  sources = [
+    "decode_test.cc",
+    "pw_tokenizer_private/tokenized_string_decoding_test_data.h",
+    "pw_tokenizer_private/varint_decoding_test_data.h",
+  ]
+  deps = [
+    ":decoder",
+    "$dir_pw_varint",
+  ]
+}
+
+pw_test("detokenize_test") {
+  sources = [
+    "detokenize_test.cc",
+  ]
+  deps = [
+    ":decoder",
+  ]
+}
+
+pw_test("hash_test") {
+  sources = [
+    "hash_test.cc",
+    "pw_tokenizer_private/generated_hash_test_cases.h",
+  ]
+  deps = [
+    ":pw_tokenizer",
+  ]
+}
+
+pw_test("token_database_test") {
+  sources = [
+    "token_database_test.cc",
+  ]
+  deps = [
+    ":decoder",
+  ]
+}
+
+pw_test("tokenize_test") {
+  sources = [
+    "pw_tokenizer_private/tokenize_test.h",
+    "tokenize_test.c",
+    "tokenize_test.cc",
+  ]
+  deps = [
+    ":pw_tokenizer",
+    "$dir_pw_varint",
+  ]
+}
+
+declare_args() {
+  # pw_java_native_interface_include_dirs specifies the paths to use for
+  # building Java Native Interface libraries. If no paths are provided, targets
+  # that require JNI may not build correctly.
+  #
+  # Example JNI include paths for a Linux system:
+  #
+  #   pw_java_native_interface_include_dirs = [
+  #     "/usr/local/buildtools/java/jdk/include/",
+  #     "/usr/local/buildtools/java/jdk/include/linux",
+  #   ]
+  #
+  pw_java_native_interface_include_dirs = []
+}
+
+# Create a shared library for the tokenizer JNI wrapper. The include paths for
+# the JNI headers must be available in the system or provided with the
+# pw_java_native_interface_include_dirs variable.
+shared_library("detokenizer_jni") {
+  public_configs = [
+    "$dir_pw_build:pw_default_cpp",
+    ":default_config",
+  ]
+  include_dirs = pw_java_native_interface_include_dirs
+  sources = [
+    "java/dev/pigweed/tokenizer/detokenizer.cc",
+  ]
+  public_deps = [
+    ":decoder",
+    "$dir_pw_preprocessor",
+  ]
+}
+
+pw_doc_group("docs") {
+  sources = [
+    "docs.rst",
+  ]
+}
diff --git a/pw_tokenizer/argument_types_test.c b/pw_tokenizer/argument_types_test.c
new file mode 100644
index 0000000..08ec027
--- /dev/null
+++ b/pw_tokenizer/argument_types_test.c
@@ -0,0 +1,102 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 C source file tests that the tokenizer argument type encoding works
+// correctly in C. These functions are called from the main C++ test file
+// argument_types_test.cc.
+#include "pw_tokenizer_private/argument_types_test.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+#error "This is a test of C code and must be compiled as C, not C++."
+#endif  // __cplusplus
+
+struct DummyType {};  // stand-in type for pointer argument type test
+
+// Check each relevant type mapping using static_asserts.
+#define CHECK_TYPE(c_type, enum_type)                     \
+  static_assert(_PW_VARARGS_TYPE((c_type)1) == enum_type, \
+                #c_type " should map to " #enum_type)
+
+// integral
+// clang-format off
+CHECK_TYPE(_Bool,              PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(char,               PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(signed char,        PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(unsigned char,      PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(short,              PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(unsigned short,     PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(int,                PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(unsigned int,       PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(long,              _PW_TOKENIZER_SELECT_INT_TYPE(long));
+CHECK_TYPE(unsigned long,     _PW_TOKENIZER_SELECT_INT_TYPE(unsigned long));
+CHECK_TYPE(long long,          PW_TOKENIZER_ARG_TYPE_INT64);
+CHECK_TYPE(unsigned long long, PW_TOKENIZER_ARG_TYPE_INT64);
+
+// floating point
+CHECK_TYPE(float,              PW_TOKENIZER_ARG_TYPE_DOUBLE);
+CHECK_TYPE(double,             PW_TOKENIZER_ARG_TYPE_DOUBLE);
+CHECK_TYPE(long double,        PW_TOKENIZER_ARG_TYPE_DOUBLE);
+
+// strings
+CHECK_TYPE(char*,              PW_TOKENIZER_ARG_TYPE_STRING);
+CHECK_TYPE(const char*,        PW_TOKENIZER_ARG_TYPE_STRING);
+
+// pointers (which should map to the appropriate sized integer)
+CHECK_TYPE(void*,             _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(const void*,       _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(signed char*,      _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(unsigned char*,    _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(int*,              _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(long long*,        _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(struct DummyType*, _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+// clang-format on
+
+// null
+static_assert(_PW_VARARGS_TYPE(NULL) == _PW_TOKENIZER_SELECT_INT_TYPE(void*),
+              "");
+
+static char char_array[16];
+
+// Define the test functions that are called by the C++ unit test.
+#define DEFINE_TEST_FUNCTION(name, ...)               \
+  pw_TokenizerArgTypes pw_TestTokenizer##name(void) { \
+    (void)char_array;                                 \
+    return PW_TOKENIZER_ARG_TYPES(__VA_ARGS__);       \
+  }
+
+DEFINE_TEST_FUNCTION(NoArgs);
+
+DEFINE_TEST_FUNCTION(Char, 'a');
+DEFINE_TEST_FUNCTION(Uint8, ((uint8_t)23));
+DEFINE_TEST_FUNCTION(Uint16, ((int16_t)100));
+DEFINE_TEST_FUNCTION(Int32, ((int32_t)1));
+DEFINE_TEST_FUNCTION(Int64, ((int64_t)0));
+DEFINE_TEST_FUNCTION(Uint64, ((uint64_t)1));
+DEFINE_TEST_FUNCTION(Float, 1e10f)
+DEFINE_TEST_FUNCTION(Double, -2.5e-50);
+DEFINE_TEST_FUNCTION(String, "const char*");
+DEFINE_TEST_FUNCTION(MutableString, ((char*)NULL));
+
+DEFINE_TEST_FUNCTION(IntFloat, 54321, ((float)0));
+DEFINE_TEST_FUNCTION(Uint64Char, ((uint64_t)0ull), ((unsigned char)'x'));
+DEFINE_TEST_FUNCTION(StringString, char_array, ((const char*)NULL));
+DEFINE_TEST_FUNCTION(Uint16Int, ((uint16_t)100), ((int)0));
+DEFINE_TEST_FUNCTION(FloatString, 100.0f, "string");
+
+DEFINE_TEST_FUNCTION(Null, NULL);
+DEFINE_TEST_FUNCTION(Pointer, ((void*)NULL));
+DEFINE_TEST_FUNCTION(PointerPointer, (int*)char_array, (void*)0);
diff --git a/pw_tokenizer/argument_types_test.cc b/pw_tokenizer/argument_types_test.cc
new file mode 100644
index 0000000..a4d2676
--- /dev/null
+++ b/pw_tokenizer/argument_types_test.cc
@@ -0,0 +1,275 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 "pw_tokenizer/internal/argument_types.h"
+
+#include <cstddef>
+
+#include "gtest/gtest.h"
+#include "pw_preprocessor/concat.h"
+#include "pw_tokenizer_private/argument_types_test.h"
+
+namespace pw::tokenizer {
+namespace {
+
+struct DummyType {};
+
+// Check each relevant type mapping.
+#define CHECK_TYPE(c_type, enum_type)                     \
+  static_assert(_PW_VARARGS_TYPE((c_type)0) == enum_type, \
+                #c_type " should map to " #enum_type)
+
+// integral
+// clang-format off
+CHECK_TYPE(bool,               PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(char,               PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(signed char,        PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(unsigned char,      PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(short,              PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(unsigned short,     PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(int,                PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(unsigned int,       PW_TOKENIZER_ARG_TYPE_INT);
+CHECK_TYPE(long,              _PW_TOKENIZER_SELECT_INT_TYPE(long));
+CHECK_TYPE(unsigned long,     _PW_TOKENIZER_SELECT_INT_TYPE(unsigned long));
+CHECK_TYPE(long long,          PW_TOKENIZER_ARG_TYPE_INT64);
+CHECK_TYPE(unsigned long long, PW_TOKENIZER_ARG_TYPE_INT64);
+
+// floating point
+CHECK_TYPE(float,              PW_TOKENIZER_ARG_TYPE_DOUBLE);
+CHECK_TYPE(double,             PW_TOKENIZER_ARG_TYPE_DOUBLE);
+CHECK_TYPE(long double,        PW_TOKENIZER_ARG_TYPE_DOUBLE);
+
+// strings
+CHECK_TYPE(char*,              PW_TOKENIZER_ARG_TYPE_STRING);
+CHECK_TYPE(const char*,        PW_TOKENIZER_ARG_TYPE_STRING);
+
+// pointers (which should map to the appropriate sized integer)
+CHECK_TYPE(void*,             _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(const void*,       _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(signed char*,      _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(unsigned char*,    _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(int*,              _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(long long*,        _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(DummyType*,        _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+
+// nullptr
+CHECK_TYPE(std::nullptr_t,    _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+static_assert(_PW_VARARGS_TYPE(nullptr) ==
+              _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+
+// clang-format on
+
+// Define a macro that generates expected values for tests. This works with
+// either 4-bit or 6-bit argument counts (for encoding types in a uint32_t or
+// uint64_t).
+#define PACKED_TYPES(...)                                                 \
+  ((PW_CONCAT(0b, __VA_ARGS__, u) << PW_TOKENIZER_TYPE_COUNT_SIZE_BITS) | \
+   PW_ARG_COUNT(__VA_ARGS__))
+
+// Test this test macro for both uint32_t and uint64_t.
+#if PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES == 4
+
+static_assert(PACKED_TYPES(00) == 0b00'0001u);
+static_assert(PACKED_TYPES(11) == 0b11'0001u);
+static_assert(PACKED_TYPES(01, 10) == 0b0110'0010u);
+static_assert(PACKED_TYPES(11, 01, 10) == 0b110110'0011u);
+static_assert(PACKED_TYPES(11, 10, 01, 00) == 0b11'10'01'00'0100u);
+
+#elif PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES == 8
+
+static_assert(PACKED_TYPES(00) == 0b00'000001u);
+static_assert(PACKED_TYPES(11) == 0b11'000001u);
+static_assert(PACKED_TYPES(01, 10) == 0b0110'000010u);
+static_assert(PACKED_TYPES(11, 01, 10) == 0b110110'000011u);
+static_assert(PACKED_TYPES(11, 10, 01, 00) == 0b11'10'01'00'000100u);
+
+#else
+
+#error "Unsupported value for PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES"
+
+#endif  // PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES
+
+#define SOME_OTHER_MACRO(...) PW_TOKENIZER_ARG_TYPES(__VA_ARGS__)
+
+TEST(ArgumentTypes, Empty) {
+  static_assert(PW_TOKENIZER_ARG_TYPES() == 0u);
+  static_assert(PW_TOKENIZER_ARG_TYPES(/* nothing here */) == 0u);
+  static_assert(SOME_OTHER_MACRO() == 0u);
+  static_assert(SOME_OTHER_MACRO(/* nothing here */) == 0u);
+}
+
+TEST(ArgumentTypes, Int32) {
+  static_assert(PW_TOKENIZER_ARG_TYPES(0) == PACKED_TYPES(00));
+  static_assert(PW_TOKENIZER_ARG_TYPES('a') == PACKED_TYPES(00));
+  static_assert(PW_TOKENIZER_ARG_TYPES(u'X') == PACKED_TYPES(00));
+  static_assert(PW_TOKENIZER_ARG_TYPES(static_cast<uint16_t>(1)) ==
+                PACKED_TYPES(00));
+}
+
+TEST(ArgumentTypes, Int64) {
+  static_assert(PW_TOKENIZER_ARG_TYPES(-123ll) == PACKED_TYPES(01));
+  static_assert(PW_TOKENIZER_ARG_TYPES(123ull) == PACKED_TYPES(01));
+  static_assert(PW_TOKENIZER_ARG_TYPES(static_cast<uint64_t>(1)) ==
+                PACKED_TYPES(01));
+}
+
+TEST(ArgumentTypes, Double) {
+  static_assert(PW_TOKENIZER_ARG_TYPES(1.0) == PACKED_TYPES(10));
+  static_assert(PW_TOKENIZER_ARG_TYPES(5.0f) == PACKED_TYPES(10));
+  float number = 9;
+  static_assert(PW_TOKENIZER_ARG_TYPES(number) == PACKED_TYPES(10));
+}
+
+TEST(ArgumentTypes, String) {
+  static_assert(PW_TOKENIZER_ARG_TYPES("string") == PACKED_TYPES(11));
+  const char buffer[2] = {'a', 'b'};
+  static_assert(PW_TOKENIZER_ARG_TYPES(buffer) == PACKED_TYPES(11));
+  char mutable_buffer[8] = {};
+  static_assert(PW_TOKENIZER_ARG_TYPES(mutable_buffer) == PACKED_TYPES(11));
+  char character = 'a';
+  static_assert(PW_TOKENIZER_ARG_TYPES(&character) == PACKED_TYPES(11));
+}
+
+TEST(ArgumentTypes, Pointer) {
+  static const bool some_data[8] = {};
+
+  // For 32-bit systems, non-string pointers show up as int32.
+  static_assert(sizeof(void*) != sizeof(int32_t) ||
+                PACKED_TYPES(00) == PW_TOKENIZER_ARG_TYPES(nullptr));
+  static_assert(sizeof(void*) != sizeof(int32_t) ||
+                PACKED_TYPES(00) ==
+                    PW_TOKENIZER_ARG_TYPES(static_cast<void*>(nullptr)));
+  static_assert(sizeof(void*) != sizeof(int32_t) ||
+                PACKED_TYPES(00) == PW_TOKENIZER_ARG_TYPES(some_data));
+
+  // For 64-bit systems, non-string pointers show up as int64.
+  static_assert(sizeof(void*) != sizeof(int64_t) ||
+                PACKED_TYPES(01) == PW_TOKENIZER_ARG_TYPES(nullptr));
+  static_assert(sizeof(void*) != sizeof(int64_t) ||
+                PACKED_TYPES(01) ==
+                    PW_TOKENIZER_ARG_TYPES(static_cast<void*>(nullptr)));
+  static_assert(sizeof(void*) != sizeof(int64_t) ||
+                PACKED_TYPES(01) == PW_TOKENIZER_ARG_TYPES(some_data));
+}
+
+TEST(ArgumentTypes, TwoArgs) {
+  static_assert(PW_TOKENIZER_ARG_TYPES(-100, 1000) == PACKED_TYPES(00, 00));
+  static_assert(PW_TOKENIZER_ARG_TYPES(-100, 10ll) == PACKED_TYPES(01, 00));
+  static_assert(PW_TOKENIZER_ARG_TYPES(-100, 1.0f) == PACKED_TYPES(10, 00));
+  static_assert(PW_TOKENIZER_ARG_TYPES(-100, "hi") == PACKED_TYPES(11, 00));
+
+  static_assert(PW_TOKENIZER_ARG_TYPES(1ull, 1000) == PACKED_TYPES(00, 01));
+  static_assert(PW_TOKENIZER_ARG_TYPES(1ull, 10ll) == PACKED_TYPES(01, 01));
+  static_assert(PW_TOKENIZER_ARG_TYPES(1ull, 1.0f) == PACKED_TYPES(10, 01));
+  static_assert(PW_TOKENIZER_ARG_TYPES(1ull, "hi") == PACKED_TYPES(11, 01));
+
+  static_assert(PW_TOKENIZER_ARG_TYPES(9.0f, 1000) == PACKED_TYPES(00, 10));
+  static_assert(PW_TOKENIZER_ARG_TYPES(9.0f, 10ll) == PACKED_TYPES(01, 10));
+  static_assert(PW_TOKENIZER_ARG_TYPES(9.0f, 1.0f) == PACKED_TYPES(10, 10));
+  static_assert(PW_TOKENIZER_ARG_TYPES(9.0f, "hi") == PACKED_TYPES(11, 10));
+
+  static_assert(PW_TOKENIZER_ARG_TYPES("!!", 1000) == PACKED_TYPES(00, 11));
+  static_assert(PW_TOKENIZER_ARG_TYPES("!!", 10ll) == PACKED_TYPES(01, 11));
+  static_assert(PW_TOKENIZER_ARG_TYPES("!!", 1.0f) == PACKED_TYPES(10, 11));
+  static_assert(PW_TOKENIZER_ARG_TYPES("!!", "hi") == PACKED_TYPES(11, 11));
+}
+
+TEST(ArgumentTypes, MultipleArgs) {
+  // clang-format off
+  static_assert(PW_TOKENIZER_ARG_TYPES(1) == 1);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2) == 2);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3) == 3);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4) == 4);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5) == 5);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6) == 6);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7) == 7);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8) == 8);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9) == 9);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) == 10);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) == 11);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) == 12);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) == 13);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) == 14);  // NOLINT
+
+#if PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES >= 8
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) == 14);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) == 15);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) == 16);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17) == 17);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18) == 18);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) == 19);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) == 20);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21) == 21);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22) == 22);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23) == 23);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24) == 24);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25) == 25);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26) == 26);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27) == 27);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28) == 28);  // NOLINT
+  static_assert(PW_TOKENIZER_ARG_TYPES(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29) == 29);  // NOLINT
+#endif  // PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES
+  // clang-format on
+}
+
+// The ArgumentTypesFromC test suite tests the arguments types macro in C. The
+// pw_Test* functions are defined in argument_types_c_test.c
+TEST(ArgumentTypesFromC, NoArgs) {
+  EXPECT_EQ(0b0000u, pw_TestTokenizerNoArgs());
+}
+
+TEST(ArgumentTypesFromC, OneArg_Int32) {
+  EXPECT_EQ(PACKED_TYPES(00), pw_TestTokenizerChar());
+  EXPECT_EQ(PACKED_TYPES(00), pw_TestTokenizerUint8());
+  EXPECT_EQ(PACKED_TYPES(00), pw_TestTokenizerUint16());
+  EXPECT_EQ(PACKED_TYPES(00), pw_TestTokenizerInt32());
+}
+
+TEST(ArgumentTypesFromC, OneArg_Int64) {
+  EXPECT_EQ(PACKED_TYPES(01), pw_TestTokenizerInt64());
+  EXPECT_EQ(PACKED_TYPES(01), pw_TestTokenizerUint64());
+}
+
+TEST(ArgumentTypesFromC, OneArg_Float) {
+  EXPECT_EQ(PACKED_TYPES(10), pw_TestTokenizerFloat());
+  EXPECT_EQ(PACKED_TYPES(10), pw_TestTokenizerDouble());
+}
+
+TEST(ArgumentTypesFromC, OneArg_String) {
+  EXPECT_EQ(PACKED_TYPES(11), pw_TestTokenizerString());
+  EXPECT_EQ(PACKED_TYPES(11), pw_TestTokenizerMutableString());
+}
+
+TEST(ArgumentTypesFromC, MultipleArgs) {
+  EXPECT_EQ(PACKED_TYPES(10, 00), pw_TestTokenizerIntFloat());
+  EXPECT_EQ(PACKED_TYPES(00, 01), pw_TestTokenizerUint64Char());
+  EXPECT_EQ(PACKED_TYPES(11, 11), pw_TestTokenizerStringString());
+  EXPECT_EQ(PACKED_TYPES(00, 00), pw_TestTokenizerUint16Int());
+  EXPECT_EQ(PACKED_TYPES(11, 10), pw_TestTokenizerFloatString());
+}
+
+TEST(ArgumentTypesFromC, Pointers) {
+  if constexpr (sizeof(void*) == sizeof(int32_t)) {
+    EXPECT_EQ(PACKED_TYPES(00), pw_TestTokenizerNull());
+    EXPECT_EQ(PACKED_TYPES(00), pw_TestTokenizerPointer());
+    EXPECT_EQ(PACKED_TYPES(00, 00), pw_TestTokenizerPointerPointer());
+  } else {  // 64-bit system
+    EXPECT_EQ(PACKED_TYPES(01), pw_TestTokenizerNull());
+    EXPECT_EQ(PACKED_TYPES(01), pw_TestTokenizerPointer());
+    EXPECT_EQ(PACKED_TYPES(01, 01), pw_TestTokenizerPointerPointer());
+  }
+}
+
+}  // namespace
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/decode.cc b/pw_tokenizer/decode.cc
new file mode 100644
index 0000000..22ce61a
--- /dev/null
+++ b/pw_tokenizer/decode.cc
@@ -0,0 +1,353 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 "pw_tokenizer/internal/decode.h"
+
+#include <algorithm>
+#include <array>
+#include <cctype>
+#include <cstring>
+
+#include "pw_varint/varint.h"
+
+namespace pw::tokenizer {
+namespace {
+
+// Functions for parsing a printf format specifier.
+size_t SkipFlags(const char* str) {
+  size_t i = 0;
+  while (str[i] == '-' || str[i] == '+' || str[i] == '#' || str[i] == ' ' ||
+         str[i] == '0') {
+    i += 1;
+  }
+  return i;
+}
+
+size_t SkipAsteriskOrInteger(const char* str) {
+  if (str[0] == '*') {
+    return 1;
+  }
+
+  size_t i = (str[0] == '-' || str[0] == '+') ? 1 : 0;
+
+  while (std::isdigit(str[i])) {
+    i += 1;
+  }
+  return i;
+}
+
+std::array<char, 2> ReadLengthModifier(const char* str) {
+  // Check for ll or hh.
+  if (str[0] == str[1] && (str[0] == 'l' || str[0] == 'h')) {
+    return {str[0], str[1]};
+  }
+  if (std::strchr("hljztL", str[0]) != nullptr) {
+    return {str[0]};
+  }
+  return {};
+}
+
+// Returns the error message that is used in place of a decoded arg when an
+// error occurs.
+std::string ErrorMessage(ArgStatus status,
+                         const std::string_view& spec,
+                         const std::string_view& value) {
+  const char* message;
+  if (status.HasError(ArgStatus::kSkipped)) {
+    message = "SKIPPED";
+  } else if (status.HasError(ArgStatus::kMissing)) {
+    message = "MISSING";
+  } else if (status.HasError(ArgStatus::kDecodeError)) {
+    message = "ERROR";
+  } else {
+    message = "INTERNAL ERROR";
+  }
+
+  std::string result(PW_TOKENIZER_ARG_DECODING_ERROR_PREFIX);
+  result.append(spec);
+  result.push_back(' ');
+  result.append(message);
+
+  if (!value.empty()) {
+    result.push_back(' ');
+    result.push_back('(');
+    result.append(value);
+    result.push_back(')');
+  }
+
+  result.append(PW_TOKENIZER_ARG_DECODING_ERROR_SUFFIX);
+  return result;
+}
+
+}  // namespace
+
+DecodedArg::DecodedArg(ArgStatus error,
+                       const std::string_view& spec,
+                       size_t raw_size_bytes,
+                       const std::string_view& value)
+    : value_(ErrorMessage(error, spec, value)),
+      spec_(spec),
+      raw_data_size_bytes_(raw_size_bytes),
+      status_(error) {}
+
+StringSegment StringSegment::ParseFormatSpec(const char* format) {
+  if (format[0] != '%' || format[1] == '\0') {
+    return StringSegment();
+  }
+
+  // Parse the format specifier.
+  size_t i = 1;
+
+  // Skip the flags.
+  i += SkipFlags(&format[i]);
+
+  // Skip the field width.
+  i += SkipAsteriskOrInteger(&format[i]);
+
+  // Skip the precision.
+  if (format[i] == '.') {
+    i += 1;
+    i += SkipAsteriskOrInteger(&format[i]);
+  }
+
+  // Read the length modifier.
+  const std::array<char, 2> length = ReadLengthModifier(&format[i]);
+  i += (length[0] == '\0' ? 0 : 1) + (length[1] == '\0' ? 0 : 1);
+
+  // Read the conversion specifier.
+  const char spec = format[i];
+
+  Type type;
+  if (spec == 's') {
+    type = kString;
+  } else if (spec == 'c' || spec == 'd' || spec == 'i') {
+    type = kSignedInt;
+  } else if (std::strchr("oxXup", spec) != nullptr) {
+    // The source size matters for unsigned integers because they need to be
+    // masked off to their correct length, since zig-zag decode sign extends.
+    // TODO(hepler): 64-bit targets likely have 64-bit l, j, z, and t. Also, p
+    // needs to be 64-bit on these targets.
+    type = length[0] == 'j' || length[1] == 'l' ? kUnsigned64 : kUnsigned32;
+  } else if (std::strchr("fFeEaAgG", spec) != nullptr) {
+    type = kFloatingPoint;
+  } else if (spec == '%' && i == 1) {
+    type = kPercent;
+  } else {
+    return StringSegment();
+  }
+
+  return {std::string_view(format, i + 1), type, VarargSize(length, spec)};
+}
+
+StringSegment::ArgSize StringSegment::VarargSize(std::array<char, 2> length,
+                                                 char spec) {
+  // Use pointer size for %p or any other type (for which this doesn't matter).
+  if (std::strchr("cdioxXu", spec) == nullptr) {
+    return VarargSize<void*>();
+  }
+  if (length[0] == 'l') {
+    return length[1] == 'l' ? VarargSize<long long>() : VarargSize<long>();
+  }
+  if (length[0] == 'j') {
+    return VarargSize<intmax_t>();
+  }
+  if (length[0] == 'z') {
+    return VarargSize<size_t>();
+  }
+  if (length[0] == 't') {
+    return VarargSize<ptrdiff_t>();
+  }
+  return VarargSize<int>();
+}
+
+DecodedArg StringSegment::DecodeString(
+    const span<const uint8_t>& arguments) const {
+  if (arguments.empty()) {
+    return DecodedArg(ArgStatus::kMissing, text_);
+  }
+
+  ArgStatus status =
+      (arguments[0] & 0x80u) == 0u ? ArgStatus::kOk : ArgStatus::kTruncated;
+
+  const uint_fast8_t size = arguments[0] & 0x7Fu;
+
+  if (arguments.size() - 1 < size) {
+    status.Update(ArgStatus::kDecodeError);
+    return DecodedArg(
+        status,
+        text_,
+        arguments.size(),
+        {reinterpret_cast<const char*>(&arguments[1]), arguments.size() - 1});
+  }
+
+  std::string value(reinterpret_cast<const char*>(&arguments[1]), size);
+
+  if (status.HasError(ArgStatus::kTruncated)) {
+    value.append("[...]");
+  }
+
+  return DecodedArg::FromValue(text_.c_str(), value.c_str(), 1 + size, status);
+}
+
+DecodedArg StringSegment::DecodeInteger(
+    const span<const uint8_t>& arguments) const {
+  if (arguments.empty()) {
+    return DecodedArg(ArgStatus::kMissing, text_);
+  }
+
+  int64_t value;
+  const size_t bytes = varint::Decode(pw::as_bytes(arguments), &value);
+
+  if (bytes == 0u) {
+    return DecodedArg(ArgStatus::kDecodeError,
+                      text_,
+                      std::min(varint::kMaxVarintSizeBytes, arguments.size()));
+  }
+
+  // Unsigned ints need to be masked to their bit width due to sign extension.
+  if (type_ == kUnsigned32) {
+    value &= 0xFFFFFFFFu;
+  }
+
+  if (local_size_ == k32Bit) {
+    return DecodedArg::FromValue(
+        text_.c_str(), static_cast<uint32_t>(value), bytes);
+  }
+  return DecodedArg::FromValue(text_.c_str(), value, bytes);
+}
+
+DecodedArg StringSegment::DecodeFloatingPoint(
+    const span<const uint8_t>& arguments) const {
+  static_assert(sizeof(float) == 4u);
+  if (arguments.size() < sizeof(float)) {
+    return DecodedArg(ArgStatus::kMissing, text_);
+  }
+
+  float value;
+  std::memcpy(&value, arguments.data(), sizeof(value));
+  return DecodedArg::FromValue(text_.c_str(), value, sizeof(value));
+}
+
+DecodedArg StringSegment::Decode(const span<const uint8_t>& arguments) const {
+  switch (type_) {
+    case kLiteral:
+      return DecodedArg(text_);
+    case kPercent:
+      return DecodedArg("%");
+    case kString:
+      return DecodeString(arguments);
+    case kSignedInt:
+    case kUnsigned32:
+    case kUnsigned64:
+      return DecodeInteger(arguments);
+    case kFloatingPoint:
+      return DecodeFloatingPoint(arguments);
+  }
+
+  return DecodedArg(ArgStatus::kDecodeError, text_);
+}
+
+DecodedArg StringSegment::Skip() const {
+  switch (type_) {
+    case kLiteral:
+      return DecodedArg(text_);
+    case kPercent:
+      return DecodedArg("%");
+    default:
+      return DecodedArg(ArgStatus::kSkipped, text_);
+  }
+}
+
+std::string DecodedFormatString::value() const {
+  std::string output;
+
+  for (const DecodedArg& arg : segments_) {
+    output.append(arg.ok() ? arg.value() : arg.spec());
+  }
+
+  return output;
+}
+
+std::string DecodedFormatString::value_with_errors() const {
+  std::string output;
+
+  for (const DecodedArg& arg : segments_) {
+    output.append(arg.value());
+  }
+
+  return output;
+}
+
+size_t DecodedFormatString::argument_count() const {
+  return std::count_if(segments_.begin(), segments_.end(), [](const auto& arg) {
+    return !arg.spec().empty();
+  });
+}
+
+size_t DecodedFormatString::decoding_errors() const {
+  return std::count_if(segments_.begin(), segments_.end(), [](const auto& arg) {
+    return !arg.ok();
+  });
+}
+
+FormatString::FormatString(const char* format) {
+  const char* text_start = format;
+
+  while (format[0] != '\0') {
+    if (StringSegment spec = StringSegment::ParseFormatSpec(format);
+        !spec.empty()) {
+      // Add the text segment seen so far (if any).
+      if (text_start < format) {
+        segments_.emplace_back(
+            std::string_view(text_start, format - text_start));
+      }
+
+      // Move along the index and text segment start.
+      format += spec.text().size();
+      text_start = format;
+
+      // Add the format specifier that was just found.
+      segments_.push_back(std::move(spec));
+    } else {
+      format += 1;
+    }
+  }
+
+  if (text_start < format) {
+    segments_.emplace_back(std::string_view(text_start, format - text_start));
+  }
+}
+
+DecodedFormatString FormatString::Format(span<const uint8_t> arguments) const {
+  std::vector<DecodedArg> results;
+  bool skip = false;
+
+  for (const auto& segment : segments_) {
+    if (skip) {
+      results.push_back(segment.Skip());
+    } else {
+      results.push_back(segment.Decode(arguments));
+      arguments = arguments.subspan(results.back().raw_size_bytes());
+
+      // If an error occurred, skip decoding the remaining arguments.
+      if (!results.back().ok()) {
+        skip = true;
+      }
+    }
+  }
+
+  return DecodedFormatString(std::move(results), arguments.size());
+}
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/decode_test.cc b/pw_tokenizer/decode_test.cc
new file mode 100644
index 0000000..d379060
--- /dev/null
+++ b/pw_tokenizer/decode_test.cc
@@ -0,0 +1,242 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 "pw_tokenizer/internal/decode.h"
+
+#include <cstdio>
+#include <string>
+#include <string_view>
+
+#include "gtest/gtest.h"
+#include "pw_tokenizer_private/tokenized_string_decoding_test_data.h"
+#include "pw_tokenizer_private/varint_decoding_test_data.h"
+#include "pw_varint/varint.h"
+
+#define ERR PW_TOKENIZER_ARG_DECODING_ERROR
+
+namespace pw::tokenizer {
+namespace {
+
+using namespace std::literals::string_view_literals;
+
+const FormatString kOneArg("Hello %s");
+const FormatString kTwoArgs("The %d %s");
+
+const bool kSupportsC99Printf = []() {
+  char buf[16] = {};
+  std::snprintf(buf, sizeof(buf), "%zu", sizeof(char));
+  return buf[0] == '1' && buf[1] == '\0';
+}();
+
+const bool kSupportsFloatPrintf = []() {
+  char buf[16] = {};
+  std::snprintf(buf, sizeof(buf), "%.1f", 3.5f);
+  return buf[0] == '3' && buf[1] == '.' && buf[2] == '5' && buf[3] == '\0';
+}();
+
+bool FormatIsSupported(std::string_view value) {
+  return (kSupportsC99Printf || (value.find("%hh") == std::string_view::npos &&
+                                 value.find("%ll") == std::string_view::npos &&
+                                 value.find("%j") == std::string_view::npos &&
+                                 value.find("%z") == std::string_view::npos &&
+                                 value.find("%t") == std::string_view::npos)) &&
+         // To make absolutely certain there are no floating point numbers, skip
+         // anything with an f or e in it.
+         (kSupportsFloatPrintf || (value.find('f') == std::string_view::npos &&
+                                   value.find('e') == std::string_view::npos));
+}
+
+TEST(TokenizedStringDecode, TokenizedStringDecodingTestCases) {
+  const auto& test_data = test::tokenized_string_decoding::kTestData;
+  static_assert(sizeof(test_data) / sizeof(*test_data) > 100u);
+
+  size_t skipped = 0;
+
+  for (const auto& [format, expected, args] : test_data) {
+    if (FormatIsSupported(format)) {
+      ASSERT_EQ(FormatString(format).Format(args).value_with_errors(),
+                expected);
+    } else {
+      skipped += 1;
+    }
+  }
+
+  if (sizeof(void*) == 8) {  // 64-bit systems should have full snprintf.
+    ASSERT_EQ(skipped, 0u);
+  }
+}
+
+TEST(TokenizedStringDecode, FullyDecodeInput_ZeroRemainingBytes) {
+  auto result = kOneArg.Format("\5hello");
+  EXPECT_EQ(result.value(), "Hello hello");
+  EXPECT_EQ(result.remaining_bytes(), 0u);
+  EXPECT_EQ(result.decoding_errors(), 0u);
+}
+
+TEST(TokenizedStringDecode, PartiallyDecodeInput_HasRemainingBytes) {
+  auto result = kOneArg.Format("\5helloworld");
+  EXPECT_EQ(result.value(), "Hello hello");
+  EXPECT_EQ(result.remaining_bytes(), 5u);
+  EXPECT_EQ(result.decoding_errors(), 0u);
+}
+
+TEST(TokenizedStringDecode, Truncation_NotAnError) {
+  auto result = kTwoArgs.Format("\6\x89musketeer");
+  EXPECT_EQ(result.value(), "The 3 musketeer[...]");
+  EXPECT_EQ(result.value_with_errors(), "The 3 musketeer[...]");
+  EXPECT_EQ(result.remaining_bytes(), 0u);
+  EXPECT_EQ(result.decoding_errors(), 0u);
+}
+
+TEST(TokenizedStringDecode, WrongStringLenth_IsErrorAndConsumesRestOfString) {
+  auto result = kTwoArgs.Format("\6\x0amusketeer");
+  EXPECT_EQ(result.value(), "The 3 %s");
+  EXPECT_EQ(result.value_with_errors(), "The 3 " ERR("%s ERROR (musketeer)"));
+  EXPECT_EQ(result.remaining_bytes(), 0u);
+  EXPECT_EQ(result.decoding_errors(), 1u);
+}
+
+TEST(TokenizedStringDecode, UnterminatedVarint_IsError) {
+  auto result = kTwoArgs.Format("\x80");
+  EXPECT_EQ(result.value(), "The %d %s");
+  EXPECT_EQ(result.value_with_errors(),
+            "The " ERR("%d ERROR") " " ERR("%s SKIPPED"));
+  EXPECT_EQ(result.remaining_bytes(), 0u);
+  EXPECT_EQ(result.decoding_errors(), 2u);
+}
+
+TEST(TokenizedStringDecode, UnterminatedVarint_ConsumesUpToMaxVarintSize) {
+  std::string_view data = "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80";
+  auto result = kTwoArgs.Format(data);
+  EXPECT_EQ(result.value(), "The %d %s");
+  EXPECT_EQ(result.value_with_errors(),
+            "The " ERR("%d ERROR") " " ERR("%s SKIPPED"));
+  EXPECT_EQ(result.remaining_bytes(),
+            data.size() - varint::kMaxVarintSizeBytes);
+  EXPECT_EQ(result.decoding_errors(), 2u);
+}
+
+TEST(TokenizedStringDecode, MissingArguments_IsDecodeError) {
+  auto result = kTwoArgs.Format("");
+  EXPECT_EQ(result.value(), "The %d %s");
+  EXPECT_EQ(result.value_with_errors(),
+            "The " ERR("%d MISSING") " " ERR("%s SKIPPED"));
+  EXPECT_EQ(result.remaining_bytes(), 0u);
+  EXPECT_EQ(result.decoding_errors(), 2u);
+}
+
+TEST(VarintDecode, VarintDecodeTestCases) {
+  const auto& test_data = test::varint_decoding::kTestData;
+  static_assert(sizeof(test_data) / sizeof(*test_data) > 100u);
+
+  size_t skipped = 0;
+
+  for (const auto& [d_fmt, d_expected, u_fmt, u_expected, data] : test_data) {
+    if (FormatIsSupported(d_fmt)) {
+      ASSERT_EQ(FormatString(d_fmt).Format(data).value(), d_expected);
+      ASSERT_EQ(FormatString(u_fmt).Format(data).value(), u_expected);
+    } else {
+      skipped += 1;
+    }
+
+    if (sizeof(void*) == 8) {  // 64-bit systems should have full snprintf.
+      ASSERT_EQ(skipped, 0u);
+    }
+  }
+}
+
+class DecodedFormatStringTest : public ::testing::Test {
+ protected:
+  DecodedFormatStringTest()
+      : no_args_("Give 110%% sometimes"),
+        one_arg_("so you can give %d%%"),
+        two_args_("%d.%d%% of the time") {}
+
+  const FormatString no_args_;
+  const FormatString one_arg_;
+  const FormatString two_args_;
+};
+
+TEST_F(DecodedFormatStringTest, Value) {
+  EXPECT_EQ("Give 110% sometimes", no_args_.Format("").value());
+  EXPECT_EQ("so you can give 0%", one_arg_.Format("\0"sv).value());
+  EXPECT_EQ("90.9% of the time", two_args_.Format("\xB4\x01\x12").value());
+}
+
+TEST_F(DecodedFormatStringTest, FormatSuccessfully_IsOk) {
+  EXPECT_TRUE(no_args_.Format("").ok());
+  EXPECT_TRUE(one_arg_.Format("\0"sv).ok());
+  EXPECT_TRUE(two_args_.Format("\xB4\x01\x12").ok());
+}
+
+TEST_F(DecodedFormatStringTest, FormatWithDecodingErrors_IsNotOkay) {
+  EXPECT_FALSE(one_arg_.Format("").ok());
+  EXPECT_FALSE(two_args_.Format("\x80").ok());
+}
+
+TEST_F(DecodedFormatStringTest, FormatWithRemainingBytes_IsNotOkay) {
+  EXPECT_FALSE(no_args_.Format("Hello").ok());
+  EXPECT_FALSE(one_arg_.Format("\0\0"sv).ok());
+  EXPECT_FALSE(two_args_.Format("\xB4\x01\x12?").ok());
+}
+
+TEST_F(DecodedFormatStringTest, FormatWithRemainingBytesAndError_IsNotOkay) {
+  EXPECT_FALSE(
+      one_arg_.Format("\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00"sv).ok());
+}
+
+TEST_F(DecodedFormatStringTest, RemainingBytes_NoData_IsZero) {
+  EXPECT_EQ(0u, no_args_.Format("").remaining_bytes());
+  EXPECT_EQ(0u, one_arg_.Format("").remaining_bytes());
+  EXPECT_EQ(0u, two_args_.Format("").remaining_bytes());
+}
+
+TEST_F(DecodedFormatStringTest, RemainingBytes_WithData_MatchesNumberOfArgs) {
+  EXPECT_EQ(2u, no_args_.Format("\x02\x02").remaining_bytes());
+  EXPECT_EQ(1u, one_arg_.Format("\x02\x02").remaining_bytes());
+  EXPECT_EQ(0u, two_args_.Format("\x02\x02").remaining_bytes());
+}
+
+TEST_F(DecodedFormatStringTest, ArgumentCount_NoData_MatchesNumberOfArgs) {
+  EXPECT_EQ(0u, no_args_.Format("").argument_count());
+  EXPECT_EQ(1u, one_arg_.Format("").argument_count());
+  EXPECT_EQ(2u, two_args_.Format("").argument_count());
+}
+
+TEST_F(DecodedFormatStringTest, ArgumentCount_WithData_MatchesNumberOfArgs) {
+  EXPECT_EQ(0u, no_args_.Format("\x02\x02").argument_count());
+  EXPECT_EQ(1u, one_arg_.Format("\x02\x02").argument_count());
+  EXPECT_EQ(2u, two_args_.Format("\x02\x02").argument_count());
+}
+
+TEST_F(DecodedFormatStringTest, DecodingErrors_NoData_MatchesArgumentCount) {
+  EXPECT_EQ(0u, no_args_.Format("").decoding_errors());
+  EXPECT_EQ(1u, one_arg_.Format("").decoding_errors());
+  EXPECT_EQ(2u, two_args_.Format("").decoding_errors());
+}
+
+TEST_F(DecodedFormatStringTest, DecodingErrors_OneArg_AllRemainingAreErrors) {
+  EXPECT_EQ(0u, no_args_.Format("\x02").decoding_errors());
+  EXPECT_EQ(0u, one_arg_.Format("\x02").decoding_errors());
+  EXPECT_EQ(1u, two_args_.Format("\x02").decoding_errors());
+}
+
+TEST_F(DecodedFormatStringTest, DecodingErrors_TwoArgs_IsZero) {
+  EXPECT_EQ(0u, no_args_.Format("\x02\x02").decoding_errors());
+  EXPECT_EQ(0u, one_arg_.Format("\x02\x02").decoding_errors());
+  EXPECT_EQ(0u, two_args_.Format("\x02\x02").decoding_errors());
+}
+
+}  // namespace
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/detokenize.cc b/pw_tokenizer/detokenize.cc
new file mode 100644
index 0000000..ef46269
--- /dev/null
+++ b/pw_tokenizer/detokenize.cc
@@ -0,0 +1,125 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 "pw_tokenizer/detokenize.h"
+
+#include <algorithm>
+
+#include "pw_tokenizer/internal/decode.h"
+
+namespace pw::tokenizer {
+namespace {
+
+std::string UnknownTokenMessage(uint32_t value) {
+  std::string output(PW_TOKENIZER_ARG_DECODING_ERROR_PREFIX "unknown token ");
+
+  // Output a hexadecimal version of the token.
+  for (int shift = 28; shift >= 0; shift -= 4) {
+    output.push_back("0123456789abcdef"[(value >> shift) & 0xF]);
+  }
+
+  output.append(PW_TOKENIZER_ARG_DECODING_ERROR_SUFFIX);
+  return output;
+}
+
+// Decoding result with the date removed, for sorting.
+using DecodingResult = std::pair<DecodedFormatString, uint32_t>;
+
+// Determines if one result is better than the other if collisions occurred.
+// Returns true if lhs is preferred over rhs. This logic should match the
+// collision resolution logic in detokenize.py.
+bool IsBetterResult(const DecodingResult& lhs, const DecodingResult& rhs) {
+  // Favor the result for which decoding succeeded.
+  if (lhs.first.ok() != rhs.first.ok()) {
+    return lhs.first.ok();
+  }
+
+  // Favor the result for which all bytes were decoded.
+  if ((lhs.first.remaining_bytes() == 0u) !=
+      (rhs.first.remaining_bytes() == 0u)) {
+    return lhs.first.remaining_bytes() == 0u;
+  }
+
+  // Favor the result with fewer decoding errors.
+  if (lhs.first.decoding_errors() != rhs.first.decoding_errors()) {
+    return lhs.first.decoding_errors() < rhs.first.decoding_errors();
+  }
+
+  // Favor the result that successfully decoded the most arguments.
+  if (lhs.first.argument_count() != rhs.first.argument_count()) {
+    return lhs.first.argument_count() > rhs.first.argument_count();
+  }
+
+  // Favor the result that was removed from the database most recently.
+  return lhs.second > rhs.second;
+}
+
+}  // namespace
+
+DetokenizedString::DetokenizedString(
+    uint32_t token,
+    const span<const TokenizedStringEntry>& entries,
+    const span<const uint8_t>& arguments)
+    : token_(token), has_token_(true) {
+  std::vector<DecodingResult> results;
+
+  for (const auto& [format, date_removed] : entries) {
+    results.push_back(DecodingResult{format.Format(arguments), date_removed});
+  }
+
+  std::sort(results.begin(), results.end(), IsBetterResult);
+
+  for (auto& result : results) {
+    matches_.push_back(std::move(result.first));
+  }
+}
+
+std::string DetokenizedString::BestString() const {
+  return matches_.empty() ? std::string() : matches_[0].value();
+}
+
+std::string DetokenizedString::BestStringWithErrors() const {
+  if (matches_.empty()) {
+    return has_token_ ? UnknownTokenMessage(token_)
+                      : PW_TOKENIZER_ARG_DECODING_ERROR("missing token");
+  }
+  return matches_[0].value_with_errors();
+}
+
+Detokenizer::Detokenizer(const TokenDatabase& database) {
+  for (const auto& entry : database) {
+    database_[entry.token].emplace_back(entry.string, entry.date_removed);
+  }
+}
+
+DetokenizedString Detokenizer::Detokenize(
+    const span<const uint8_t>& encoded) const {
+  // The token is missing from the encoded data; there is nothing to do.
+  if (encoded.size() < sizeof(uint32_t)) {
+    return DetokenizedString();
+  }
+
+  const uint32_t token =
+      encoded[3] << 24 | encoded[2] << 16 | encoded[1] << 8 | encoded[0];
+
+  const auto result = database_.find(token);
+
+  return DetokenizedString(token,
+                           result == database_.end()
+                               ? span<TokenizedStringEntry>()
+                               : span(result->second),
+                           encoded.subspan(sizeof(token)));
+}
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/detokenize_test.cc b/pw_tokenizer/detokenize_test.cc
new file mode 100644
index 0000000..688cf7a
--- /dev/null
+++ b/pw_tokenizer/detokenize_test.cc
@@ -0,0 +1,289 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 "pw_tokenizer/detokenize.h"
+
+#include <string_view>
+
+#include "gtest/gtest.h"
+
+namespace pw::tokenizer {
+namespace {
+
+using namespace std::literals::string_view_literals;
+
+// Use a shorter name for the error string macro.
+#define ERR PW_TOKENIZER_ARG_DECODING_ERROR
+
+// Use alignas to ensure that the data is properly aligned to be read from a
+// token database entry struct. This avoids unaligned memory reads.
+alignas(TokenDatabase::RawEntry) constexpr char kBasicData[] =
+    "TOKENS\0\0"
+    "\x04\x00\x00\x00"
+    "\0\0\0\0"
+    "\x01\x00\x00\x00----"
+    "\x05\x00\x00\x00----"
+    "\xFF\x00\x00\x00----"
+    "\xFF\xEE\xEE\xDD----"
+    "One\0"
+    "TWO\0"
+    "333\0"
+    "FOUR";
+
+class Detokenize : public ::testing::Test {
+ protected:
+  Detokenize() : detok_(TokenDatabase::Create<kBasicData>()) {}
+  Detokenizer detok_;
+};
+
+TEST_F(Detokenize, NoFormatting) {
+  EXPECT_EQ(detok_.Detokenize("\1\0\0\0"sv).BestString(), "One");
+  EXPECT_EQ(detok_.Detokenize("\5\0\0\0"sv).BestString(), "TWO");
+  EXPECT_EQ(detok_.Detokenize("\xff\x00\x00\x00"sv).BestString(), "333");
+  EXPECT_EQ(detok_.Detokenize("\xff\xee\xee\xdd"sv).BestString(), "FOUR");
+}
+
+TEST_F(Detokenize, BestString_MissingToken_IsEmpty) {
+  EXPECT_FALSE(detok_.Detokenize("").ok());
+  EXPECT_TRUE(detok_.Detokenize("", 0u).BestString().empty());
+  EXPECT_TRUE(detok_.Detokenize("\1", 1u).BestString().empty());
+  EXPECT_TRUE(detok_.Detokenize("\1\0"sv).BestString().empty());
+  EXPECT_TRUE(detok_.Detokenize("\1\0\0"sv).BestString().empty());
+  EXPECT_TRUE(detok_.Detokenize("\0\0\0"sv).BestString().empty());
+}
+
+TEST_F(Detokenize, BestString_UnknownToken_IsEmpty) {
+  EXPECT_FALSE(detok_.Detokenize("\0\0\0\0"sv).ok());
+  EXPECT_TRUE(detok_.Detokenize("\0\0\0\0"sv).BestString().empty());
+  EXPECT_TRUE(detok_.Detokenize("\2\0\0\0"sv).BestString().empty());
+  EXPECT_TRUE(detok_.Detokenize("\x10\x32\x54\x76\x99"sv).BestString().empty());
+  EXPECT_TRUE(detok_.Detokenize("\x98\xba\xdc\xfe"sv).BestString().empty());
+}
+
+TEST_F(Detokenize, BestStringWithErrors_MissingToken_ErrorMessage) {
+  EXPECT_FALSE(detok_.Detokenize("").ok());
+  EXPECT_EQ(detok_.Detokenize("", 0u).BestStringWithErrors(),
+            ERR("missing token"));
+  EXPECT_EQ(detok_.Detokenize("\1", 1u).BestStringWithErrors(),
+            ERR("missing token"));
+  EXPECT_EQ(detok_.Detokenize("\1\0"sv).BestStringWithErrors(),
+            ERR("missing token"));
+  EXPECT_EQ(detok_.Detokenize("\1\0\0"sv).BestStringWithErrors(),
+            ERR("missing token"));
+  EXPECT_EQ(detok_.Detokenize("\0\0\0"sv).BestStringWithErrors(),
+            ERR("missing token"));
+}
+
+TEST_F(Detokenize, BestStringWithErrors_UnknownToken_ErrorMessage) {
+  EXPECT_FALSE(detok_.Detokenize("\0\0\0\0"sv).ok());
+  EXPECT_EQ(detok_.Detokenize("\0\0\0\0"sv).BestStringWithErrors(),
+            ERR("unknown token 00000000"));
+  EXPECT_EQ(detok_.Detokenize("\2\0\0\0"sv).BestStringWithErrors(),
+            ERR("unknown token 00000002"));
+  EXPECT_EQ(detok_.Detokenize("\x10\x32\x54\x76\x99"sv).BestStringWithErrors(),
+            ERR("unknown token 76543210"));
+  EXPECT_EQ(detok_.Detokenize("\x98\xba\xdc\xfe"sv).BestStringWithErrors(),
+            ERR("unknown token fedcba98"));
+}
+
+alignas(TokenDatabase::RawEntry) constexpr char kDataWithArguments[] =
+    "TOKENS\0\0"
+    "\x09\x00\x00\x00"
+    "\0\0\0\0"
+    "\x00\x00\x00\x00----"
+    "\x0A\x0B\x0C\x0D----"
+    "\x0E\x0F\x00\x01----"
+    "\xAA\xAA\xAA\xAA----"
+    "\xBB\xBB\xBB\xBB----"
+    "\xCC\xCC\xCC\xCC----"
+    "\xDD\xDD\xDD\xDD----"
+    "\xEE\xEE\xEE\xEE----"
+    "\xFF\xFF\xFF\xFF----"
+    "\0"
+    "Use the %s, %s.\0"
+    "Now there are %d of %s!\0"
+    "%c!\0"    // AA
+    "%hhu!\0"  // BB
+    "%hu!\0"   // CC
+    "%u!\0"    // DD
+    "%lu!\0"   // EE
+    "%llu!";   // FF
+
+constexpr TokenDatabase kWithArgs = TokenDatabase::Create<kDataWithArguments>();
+
+using Case = std::pair<std::string_view, std::string_view>;
+
+template <typename... Args>
+auto TestCases(Args... args) {
+  return std::array<Case, sizeof...(Args)>{args...};
+}
+
+class DetokenizeWithArgs : public ::testing::Test {
+ protected:
+  DetokenizeWithArgs() : detok_(kWithArgs) {}
+
+  Detokenizer detok_;
+};
+
+TEST_F(DetokenizeWithArgs, NoMatches) {
+  EXPECT_TRUE(detok_.Detokenize("\x23\xab\xc9\x87"sv).matches().empty());
+}
+
+TEST_F(DetokenizeWithArgs, SingleMatch) {
+  EXPECT_EQ(detok_.Detokenize("\x00\x00\x00\x00"sv).matches().size(), 1u);
+}
+
+TEST_F(DetokenizeWithArgs, Empty) {
+  EXPECT_EQ(detok_.Detokenize("\x00\x00\x00\x00"sv).BestString(), "");
+}
+
+TEST_F(DetokenizeWithArgs, Successful) {
+  // Run through test cases, but don't include cases that use %hhu or %llu since
+  // these are not currently supported in arm-none-eabi-gcc.
+  for (auto [data, expected] : TestCases(
+           Case{"\x0A\x0B\x0C\x0D\5force\4Luke"sv, "Use the force, Luke."},
+           Case{"\x0E\x0F\x00\x01\4\4them"sv, "Now there are 2 of them!"},
+           Case{"\xAA\xAA\xAA\xAA\xfc\x01"sv, "~!"},
+           Case{"\xCC\xCC\xCC\xCC\xfe\xff\x07"sv, "65535!"},
+           Case{"\xDD\xDD\xDD\xDD\xfe\xff\x07"sv, "65535!"},
+           Case{"\xDD\xDD\xDD\xDD\xfe\xff\xff\xff\x1f"sv, "4294967295!"},
+           Case{"\xEE\xEE\xEE\xEE\xfe\xff\x07"sv, "65535!"},
+           Case{"\xEE\xEE\xEE\xEE\xfe\xff\xff\xff\x1f"sv, "4294967295!"})) {
+    EXPECT_EQ(detok_.Detokenize(data).BestString(), expected);
+  }
+}
+
+TEST_F(DetokenizeWithArgs, ExtraDataError) {
+  auto error = detok_.Detokenize("\x00\x00\x00\x00MORE data"sv);
+  EXPECT_FALSE(error.ok());
+  EXPECT_EQ("", error.BestString());
+}
+
+TEST_F(DetokenizeWithArgs, MissingArgumentError) {
+  auto error = detok_.Detokenize("\x0A\x0B\x0C\x0D\5force"sv);
+  EXPECT_FALSE(error.ok());
+  EXPECT_EQ(error.BestString(), "Use the force, %s.");
+  EXPECT_EQ(error.BestStringWithErrors(),
+            "Use the force, " ERR("%s MISSING") ".");
+}
+
+TEST_F(DetokenizeWithArgs, DecodingError) {
+  auto error = detok_.Detokenize("\x0E\x0F\x00\x01\xFF"sv);
+  EXPECT_FALSE(error.ok());
+  EXPECT_EQ(error.BestString(), "Now there are %d of %s!");
+  EXPECT_EQ(error.BestStringWithErrors(),
+            "Now there are " ERR("%d ERROR") " of " ERR("%s SKIPPED") "!");
+}
+
+alignas(TokenDatabase::RawEntry) constexpr char kDataWithCollisions[] =
+    "TOKENS\0\0"
+    "\x0F\x00\x00\x00"
+    "\0\0\0\0"
+    "\x00\x00\x00\x00\xff\xff\xff\xff"  // 1
+    "\x00\x00\x00\x00\x01\x02\x03\x04"  // 2
+    "\x00\x00\x00\x00\xff\xff\xff\xff"  // 3
+    "\x00\x00\x00\x00\xff\xff\xff\xff"  // 4
+    "\x00\x00\x00\x00\xff\xff\xff\xff"  // 5
+    "\x00\x00\x00\x00\xff\xff\xff\xff"  // 6
+    "\x00\x00\x00\x00\xff\xff\xff\xff"  // 7
+    "\xAA\xAA\xAA\xAA\x00\x00\x00\x00"  // 8
+    "\xAA\xAA\xAA\xAA\xff\xff\xff\xff"  // 9
+    "\xBB\xBB\xBB\xBB\xff\xff\xff\xff"  // A
+    "\xBB\xBB\xBB\xBB\xff\xff\xff\xff"  // B
+    "\xCC\xCC\xCC\xCC\xff\xff\xff\xff"  // C
+    "\xCC\xCC\xCC\xCC\xff\xff\xff\xff"  // D
+    "\xDD\xDD\xDD\xDD\xff\xff\xff\xff"  // E
+    "\xDD\xDD\xDD\xDD\xff\xff\xff\xff"  // F
+    // String table
+    "This string is present\0"   // 1
+    "This string is removed\0"   // 2
+    "One arg %d\0"               // 3
+    "One arg %s\0"               // 4
+    "Two args %s %u\0"           // 5
+    "Two args %s %s %% %% %%\0"  // 6
+    "Four args %d %d %d %d\0"    // 7
+    "This one is removed\0"      // 8
+    "This one is present\0"      // 9
+    "Two ints %d %d\0"           // A
+    "Three ints %d %d %d\0"      // B
+    "Three strings %s %s %s\0"   // C
+    "Two strings %s %s\0"        // D
+    "Three %s %s %s\0"           // E
+    "Five %d %d %d %d %s\0";     // F
+
+constexpr TokenDatabase kWithCollisions =
+    TokenDatabase::Create<kDataWithCollisions>();
+
+class DetokenizeWithCollisions : public ::testing::Test {
+ protected:
+  DetokenizeWithCollisions() : detok_(kWithCollisions) {}
+
+  Detokenizer detok_;
+};
+
+TEST_F(DetokenizeWithCollisions, Collision_AlwaysPreferSuccessfulDecode) {
+  for (auto [data, expected] :
+       TestCases(Case{"\0\0\0\0"sv, "This string is present"},
+                 Case{"\0\0\0\0\x01"sv, "One arg -1"},
+                 Case{"\0\0\0\0\x80"sv, "One arg [...]"},
+                 Case{"\0\0\0\0\4Hey!\x04"sv, "Two args Hey! 2"})) {
+    EXPECT_EQ(detok_.Detokenize(data).BestString(), expected);
+  }
+}
+
+TEST_F(DetokenizeWithCollisions, Collision_PreferDecodingAllBytes) {
+  for (auto [data, expected] :
+       TestCases(Case{"\0\0\0\0\x80\x80\x80\x80\x00"sv, "Two args [...] 0"},
+                 Case{"\0\0\0\0\x08?"sv, "One arg %s"},
+                 Case{"\0\0\0\0\x01!\x01\x80"sv, "Two args ! \x80 % % %"})) {
+    EXPECT_EQ(detok_.Detokenize(data).BestString(), expected);
+  }
+}
+
+TEST_F(DetokenizeWithCollisions, Collision_PreferFewestDecodingErrors) {
+  for (auto [data, expected] :
+       TestCases(Case{"\xBB\xBB\xBB\xBB\x00"sv, "Two ints 0 %d"},
+                 Case{"\xCC\xCC\xCC\xCC\2Yo\5?"sv, "Two strings Yo %s"})) {
+    EXPECT_EQ(detok_.Detokenize(data).BestString(), expected);
+  }
+}
+
+TEST_F(DetokenizeWithCollisions, Collision_PreferMostDecodedArgs) {
+  auto result = detok_.Detokenize("\xDD\xDD\xDD\xDD\x01\x02\x01\x04\x05"sv);
+  EXPECT_EQ((std::string_view)result.matches()[0].value(), "Five -1 1 -1 2 %s");
+  EXPECT_EQ((std::string_view)result.matches()[1].value(), "Three \2 \4 %s"sv);
+}
+
+TEST_F(DetokenizeWithCollisions, Collision_PreferMostDecodedArgs_NoPercent) {
+  // The "Two args %s %s ..." string successfully decodes this, and has more
+  // "arguments", because of %%, but %% doesn't count as as a decoded argument.
+  EXPECT_EQ(detok_.Detokenize("\0\0\0\0\x01\x00\x01\x02"sv).BestString(),
+            "Four args -1 0 -1 1");
+}
+
+TEST_F(DetokenizeWithCollisions, Collision_PreferStillPresentString) {
+  for (auto [data, expected] :
+       TestCases(Case{"\x00\x00\x00\x00"sv, "This string is present"},
+                 Case{"\xAA\xAA\xAA\xAA"sv, "This one is present"})) {
+    EXPECT_EQ(detok_.Detokenize(data).BestString(), expected);
+  }
+}
+
+TEST_F(DetokenizeWithCollisions, Collision_TracksAllMatches) {
+  auto result = detok_.Detokenize("\0\0\0\0"sv);
+  EXPECT_EQ(result.matches().size(), 7u);
+}
+
+}  // namespace
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/docs.rst b/pw_tokenizer/docs.rst
new file mode 100644
index 0000000..71abfeb
--- /dev/null
+++ b/pw_tokenizer/docs.rst
@@ -0,0 +1,499 @@
+.. _chapter-tokenizer:
+
+.. default-domain:: cpp
+
+.. highlight:: sh
+
+---------
+Tokenizer
+---------
+The tokenizer module provides facilities for converting strings to binary
+tokens. String literals are replaced with integer tokens in the firmware image,
+which can be decoded off-device to restore the original string. Strings may be
+printf-style format strings and include arguments, such as ``"My name is %s"``.
+Arguments are encoded into compact binary form at runtime.
+
+.. note::
+  This usage of the term "tokenizer" is not related to parsing! The
+  module is called tokenizer because it replaces a whole string literal with an
+  integer token. It does not parse strings into separate tokens.
+
+The most common application of the tokenizer module is binary logging, and its
+designed to integrate easily into existing logging systems. However, the
+tokenizer is general purpose and can be used to tokenize any strings.
+
+**Why tokenize strings?**
+
+  * Dramatically reduce binary size by removing string literals from binaries.
+  * Reduce CPU usage by replacing snprintf calls with simple tokenization code.
+  * Reduce I/O traffic, RAM, and flash usage by sending and storing compact
+    tokens instead of strings.
+  * Remove potentially sensitive log, assert, and other strings from binaries.
+
+Basic operation
+===============
+  1. In C or C++ code, strings are hashed to generate a stable 32-bit token.
+  2. The tokenization macro removes the string literal by placing it in an ELF
+     section that is excluded from the final binary.
+  3. Strings are extracted from an ELF to build a database of tokenized strings
+     for use by the detokenizer. The ELF file may also be used directly.
+  4. During operation, the device encodes the string token and its arguments, if
+     any.
+  5. The encoded tokenized strings are sent off-device or stored.
+  6. Off-device, the detokenizer tools use the token database or ELF files to
+     detokenize the strings to human-readable form.
+
+Module usage
+============
+There are two sides to tokenization: tokenizing strings in the source code and
+detokenizing these strings back to human-readable form.
+
+Tokenization
+------------
+Tokenization converts a string literal to a token. If it's a printf-style
+string, its arguments are encoded along with it. The results of tokenization can
+be sent off device or stored in place of a full string.
+
+Adding tokenization to a project is simple. To tokenize a string, include
+``pw_tokenizer/tokenize.h`` and invoke a ``PW_TOKENIZE_`` macro.
+
+To tokenize a string literal, invoke ``PW_TOKENIZE_STRING``. This macro returns
+a ``uint32_t`` token.
+
+.. code-block:: cpp
+
+  constexpr uint32_t token = PW_TOKENIZE_STRING("Any string literal!");
+
+Format strings are tokenized into a fixed-size buffer. The buffer contains the
+``uint32_t`` token followed by the encoded form of the arguments, if any. The
+most flexible tokenization macro is ``PW_TOKENIZE_TO_BUFFER``, which encodes to
+a caller-provided buffer.
+
+.. code-block:: cpp
+
+  uint8_t buffer[BUFFER_SIZE];
+  size_t size_bytes = sizeof(buffer);
+  PW_TOKENIZE_TO_BUFFER(buffer, &size_bytes, format_string_literal, args...);
+
+While ``PW_TOKENIZE_TO_BUFFER`` is flexible, its per-use code size overhead is
+larger than its alternatives. ``PW_TOKENIZE_TO_CALLBACK`` tokenizes to a buffer
+on the stack and calls a ``void(const uint8_t* buffer, size_t buffer_size)``
+callback that is provided at the call site. The size of the buffer is set with
+``PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES``.
+
+.. code-block:: cpp
+
+  PW_TOKENIZE_TO_CALLBACK(HandlerFunction, "Format string: %x", arg);
+
+``PW_TOKENIZE_TO_GLOBAL_HANDLER`` is the most efficient tokenization function,
+since it takes the fewest arguments. Like the callback form, it encodes to a
+buffer on the stack. It then calls the C-linkage function
+``pw_TokenizerHandleEncodedMessage``, which must be defined by the project.
+
+.. code-block:: cpp
+
+  PW_TOKENIZE_TO_GLOBAL_HANDLER(format_string_literal, arguments...);
+
+  void pw_TokenizerHandleEncodedMessage(const uint8_t encoded_message[],
+                                        size_t size_bytes);
+
+``PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD`` is similar, but passes a
+``void*`` argument to the global handler function. This can be used to pass a
+log level or other metadata along with the tokenized string.
+
+.. code-block:: cpp
+
+  PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(payload,
+                                             format_string_literal,
+                                             args...);
+
+  void pw_TokenizerHandleEncodedMessageWithPayload(void* payload,
+                                                   const uint8_t encoded_message[],
+                                                   size_t size_bytes);
+
+.. tip::
+  ``%s`` arguments are inefficient to encode and can quickly fill a tokenization
+  buffer. Keep ``%s`` arguments short or avoid encoding them as strings if
+  possible. See `Tokenized strings as %s arguments`_.
+
+Example: binary logging
+^^^^^^^^^^^^^^^^^^^^^^^
+String tokenization is perfect for logging. Consider the following log macro,
+which gathers the file, line number, and log message. It calls the ``RecordLog``
+function, which formats the log string, collects a timestamp, and transmits the
+result.
+
+.. code-block:: cpp
+
+  #define LOG_INFO(format, ...) \
+      RecordLog(LogLevel_INFO, __FILE_NAME__, __LINE__, format, ##__VA_ARGS__)
+
+  void RecordLog(LogLevel level, const char* file, int line, const char* format,
+                 ...) {
+    if (level < current_log_level) {
+      return;
+    }
+
+    int bytes = snprintf(buffer, sizeof(buffer), "%s:%d ", file, line);
+
+    va_list args;
+    va_start(args, format);
+    bytes += vsnprintf(&buffer[bytes], sizeof(buffer) - bytes, format, args);
+    va_end(args);
+
+    TransmitLog(TimeSinceBootMillis(), buffer, size);
+  }
+
+It is trivial to convert this to a binary log using the tokenizer. The
+``RecordLog`` call is replaced with a
+``PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD`` invocation. The
+``pw_TokenizerHandleEncodedMessageWithPayload`` implementation collects the
+timestamp and transmits the message with ``TransmitLog``.
+
+.. code-block:: cpp
+
+  #define LOG_INFO(format, ...)                   \
+      PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD( \
+          (void*)LogLevel_INFO,                   \
+          __FILE_NAME__ ":%d " format,            \
+          __LINE__,                               \
+          __VA_ARGS__);                           \
+
+  extern "C" void pw_TokenizerHandleEncodedMessageWithPayload(
+      void* level, const uint8_t encoded_message[], size_t size_bytes) {
+    if (reinterpret_cast<LogLevel>(level) >= current_log_level) {
+      TransmitLog(TimeSinceBootMillis(), encoded_message, size_bytes);
+    }
+  }
+
+Note that the ``__FILE_NAME__`` string is directly included in the log format
+string. Since the string is tokenized, this has no effect on binary size. A
+``%d`` for the line number is added to the format string, so that changing the
+line of the log message does not generate a new token. There is no overhead for
+additional tokens, but it may not be desireable to fill a token database with
+duplicate log lines.
+
+Database management
+^^^^^^^^^^^^^^^^^^^
+Token databases store a mapping of tokens to the strings they represent. An ELF
+file can be used as a token database, but it only contains the strings for its
+exact build. A token database file aggregates tokens from multiple ELF files, so
+that a single database can decode tokenized strings from any known ELF.
+
+Creating and maintaining a token database is simple. Token databases are managed
+with the ``database.py`` script. The ``create`` command makes a new token
+database from ELF files or other databases.
+
+.. code-block:: sh
+
+  ./database.py create --database DATABASE_NAME ELF_OR_DATABASE_FILE...
+
+Two database formats are supported: CSV and binary. Provide ``--type binary`` to
+``create`` generate a binary database instead of the default CSV. CSV databases
+are great for checking into a source control or for human review. Binary
+databases are more compact and simpler to parse. The C++ detokenizer library
+only supports binary databases currently.
+
+As new tokenized strings are added, update the database with the add command.
+
+.. code-block:: sh
+
+  ./database.py add --database DATABASE_NAME ELF_OR_DATABASE_FILE...
+
+A CSV token database can be checked into a source repository and updated as code
+changes are made. The build system can invoke ``database.py`` to update the
+database after each build.
+
+Detokenization
+--------------
+Detokenization is the process of expanding a token to the string it represents
+and decoding its arguments. This module provides Python and C++ detokenization
+libraries.
+
+Python
+^^^^^^
+To detokenize in Python, import Detokenizer from the ``pw_tokenizer`` package,
+and instantiate it with paths to token databases or ELF files.
+
+.. code-block:: python
+
+  import pw_tokenizer
+
+  detokenizer = pw_tokenizer.Detokenizer('path/to/database.csv', 'other/path.elf')
+
+  def process_log_message(log_message):
+    result = detokenizer.detokenize(log_message.payload)
+    self._log(str(result))
+
+The ``pw_tokenizer`` pacakge also provices the ``AutoUpdatingDetokenizer``
+class, which can be used in place of the standard ``Detokenizer``. This class
+monitors database files for changes and automatically reloads them when they
+change. This is helpful for long-running tools that use detokenization.
+
+C++
+^^^
+The C++ detokenization libraries can be used in C++ or any language that can
+call into C++ with a C-linkage wrapper, such as Java or Rust. A reference
+Android Java JNI is provided.
+
+The C++ detokenization library uses binary-format token databases (created with
+``--type binary``). Read a binary format database from a file or include it in
+the source code. Pass the database array to ``TokenDatabase::Create``, and
+construct a detokenizer.
+
+.. code-block:: cpp
+
+  Detokenizer detokenizer(TokenDatabase::Create(token_database_array));
+
+  std::string ProcessLog(span<uint8_t> log_data) {
+    return detokenizer.Detokenize(log_data).BestString();
+  }
+
+The ``TokenDatabase`` class verifies that its data is valid before using it. If
+it is invalid, the ``TokenDatabase::Create`` returns an empty database for which
+``ok()`` returns false. If the token database is included in the source code,
+this check can be done at compile time.
+
+.. code-block:: cpp
+
+  // This line fails to compile with a static_assert if the database is invalid.
+  constexpr TokenDatabase kDefaultDatabase =  TokenDatabase::Create<kData>();
+
+  Detokenizer OpenDatabase(std::string_view path) {
+    std::vector<uint8_t> data = ReadWholeFile(path);
+
+    TokenDatabase database = TokenDatabase::Create(data);
+
+    // This checks if the file contained a valid database. It is safe to use a
+    // TokenDatabase that failed to load (it will be empty), but it may be
+    // desirable to provide a default database or otherwise handle the error.
+    if (database.ok()) {
+      return Detokenizer(database);
+    }
+    return Detokenizer(kDefaultDatabase);
+  }
+
+Token generation: fixed length hashing at compile time
+======================================================
+String tokens are generated using a modified version of the x65599 hash used by
+the SDBM project. All hashing is done at compile time.
+
+In C code, strings are hashed with a preprocessor macro. For compatibility with
+macros, the hash must be limited to a fixed maximum number of characters. This
+value is set by ``PW_TOKENIZER_CFG_HASH_LENGTH``.
+
+Increasing ``PW_TOKENIZER_CFG_HASH_LENGTH`` increases the compilation time for C
+due to the complexity of the hashing macros. C++ macros use a constexpr
+function instead of a macro, so the compilation time impact is minimal. Projects
+primarily in C++ should use a large value for ``PW_TOKENIZER_CFG_HASH_LENGTH``
+(perhaps even ``std::numeric_limits<size_t>::max()``).
+
+Base64 format
+=============
+The tokenizer defaults to a compact binary representation of tokenized messages.
+Applications may desire a textual representation of tokenized strings. This
+makes it easy to use tokenized messages alongside plain text messages, but comes
+at an efficiency cost.
+
+The tokenizer module supports prefixed Base64-encoded messages: a single
+character ($) followed by the Base64-encoded message. For example, the token
+0xabcdef01 followed by the argument 0x05 would be encoded as ``01 ef cd ab 05``
+in binary and ``$Ae/NqwU=`` in Base64.
+
+Base64 decoding is supported in the Python detokenizer through the
+``detokenize_base64`` and related functions. Base64 encoding and decoding are
+not yet supprted in C++, but it is straightforward to add Base64 encoding with
+any Base64 library.
+
+.. tip::
+  The detokenization tools support recursive detokenization for prefixed Base64
+  text. Tokenized strings found in detokenized text are detokenized, so
+  prefixed Base64 messages can be passed as ``%s`` arguments.
+
+  For example, the message ``"$d4ffJaRn`` might be passed as the argument to a
+  ``"Nested message: %s"`` string. The detokenizer would decode the message in
+  two steps:
+
+  ::
+
+   "$alRZyuk2J3v=" → "Nested message: $d4ffJaRn" → "Nested message: Wow!"
+
+War story: deploying tokenized logging to an existing product
+=============================================================
+The tokenizer module was developed to bring tokenized logging to an
+in-development product. The product is complex, with several interacting
+microcontrollers. It already had an established text-based logging system.
+Deploying tokenization was straightforward and had substantial benefits.
+
+**Results**
+  * Log contents shrunk by over 50%, even with Base64 encoding.
+
+    * Significant size savings for encoded logs, even using the less-efficient
+      Base64 encoding required for compatibility with the existing log system.
+    * Freed valueable communication bandwidth.
+    * Allowed storing many more logs in crash dumps.
+
+  * Substantial flash savings.
+
+    * Reduced the size of 115 KB and 172 KB firmware images by over 20 KB each.
+    * Shaved over 100 KB from a large 2 MB image.
+
+  * Simpler logging code.
+
+    * Removed CPU-heavy ``snprintf`` calls.
+    * Removed complex code for forwarding log arguments to a low-priority task.
+
+This section describes the tokenizer deployment process and highlights key
+insights.
+
+Firmware deployment
+-------------------
+  * In the project's logging macro, calls to the underlying logging function
+    were replaced with a ``PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD``
+    invocation.
+  * The log level was passed as the payload argument to facilitate runtime log
+    level control.
+  * For this project, it was necessary to encode the log messages as text. In
+    ``pw_TokenizerHandleEncodedMessageWithPayload``, the log messages were
+    encoded in the $-prefixed `Base64 format`_, then dispatched as normal log
+    messages.
+  * Asserts were tokenized using ``PW_TOKENIZE_TO_CALLBACK``.
+
+.. attention::
+  Do not encode line numbers in tokenized strings. This results in a huge
+  number of lines being added to the database, since every time code moves,
+  new strings are tokenized. If line numbers are desired in a tokenized
+  string, add a ``"%d"`` to the string and pass ``__LINE__`` as an argument.
+
+Database management
+-------------------
+  * The token database was stored as a CSV file in the project's Git repo.
+  * The token database was automatically updated as part of the build, and
+    developers were expected to check in the database changes alongside their
+    code changes.
+  * A presubmit check verified that all strings added by a change were added to
+    the token database.
+  * The token database included logs and asserts for all firmware images in the
+    project.
+  * No strings were purged from the token database.
+
+.. tip::
+  Merge conflicts may be a frequent occurrence with an in-source database. If
+  the database is in-source, make sure there is a simple script to resolve any
+  merge conflicts. The script could either keep both sets of lines or discard
+  local changes and regenerate the database.
+
+Decoding tooling deployment
+---------------------------
+  * The Python detokenizer in ``pw_tokenizer`` was deployed to two places:
+
+      * Product-specific Python command line tools, using
+        ``pw_tokenizer.Detokenizer``.
+      * Standalone script for decoding prefixed Base64 tokens in files or
+        live output (e.g. from ``adb``), using ``detokenize.py``'s command line
+        interface.
+
+  * The C++ detokenizer library was deployed to two Android apps with a Java
+    Native Interface (JNI) layer.
+
+      * The binary token database was included as a raw resource in the APK.
+      * In one app, the built-in token database could be overridden by copying a
+        file to the phone.
+
+.. tip:: Make the tokenized logging tools simple to use.
+
+  * Provide simple wrapper shell scripts that fill in arguments for the
+    project. For example, point ``detokenize.py`` to the project's token
+    databases.
+  * Use ``pw_tokenizer.AutoReloadingDetokenizer`` to decode in
+    continuously-running tools, so that users don't have to restart the tool
+    when the token database updates.
+  * Integrate detokenization everywhere it is needed. Integrating the tools
+    takes just a few lines of code, and token databases can be embedded in
+    APKs or binaries.
+
+Limitations and future work
+===========================
+
+GCC bug: tokenization in template functions
+-------------------------------------------
+GCC incorrectly ignores the section attribute for template
+`functions <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70435>`_ and
+`variables <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88061>`_. Due to this
+bug, tokenized strings in template functions may be emitted into ``.rodata``
+instead of the special tokenized string section. This causes two problems:
+
+  1. Tokenized strings will not be discovered by the token database tools.
+  2. Tokenized strings may not be removed from the final binary.
+
+clang does **not** have this issue! Use clang if you can.
+
+It is possible to work around this bug in GCC. One approach would be to tag
+format strings so that the database tools can find them in ``.rodata``. Then, to
+remove the strings, compile two binaries: one metadata binary with all tokenized
+strings and a second, final binary that removes the strings. The strings could
+be removed by providing the appropriate linker flags or by removing the ``used``
+attribute from the tokenized string character array declaration.
+
+64-bit tokenization
+-------------------
+The Python and C++ detokenizing libraries currently assume that strings were
+tokenized on a system with 32-bit ``long``, ``size_t``, ``intptr_t``, and
+``ptrdiff_t``. Decoding may not work correctly for these types if a 64-bit
+device performed the tokenization.
+
+Supporting detokenization of strings tokenized on 64-bit targets would be
+simple. This could be done by adding an option to switch the 32-bit types to
+64-bit. The tokenizer stores the sizes of these types in the ``.tokenizer_info``
+ELF section, so the sizes of these types can be verified by checking the ELF
+file, if necessary.
+
+Tokenization in headers
+-----------------------
+Tokenizing code in header files (inline functions or templates) may trigger
+warnings such as ``-Wlto-type-mismatch`` under certain conditions. That
+is because tokenization requires declaring a character array for each tokenized
+string. If the tokenized string includes macros that change value, the size of
+this character array changes, which means the same static variable is defined
+with different sizes. It should be safe to suppress these warnings, but, when
+possible, code that tokenizes strings with macros that can change value should
+be moved to source files rather than headers.
+
+Tokenized strings as ``%s`` arguments
+-------------------------------------
+Encoding ``%s`` string arguments is inefficient, since ``%s`` strings are
+encoded 1:1, with no tokenization. It would be better to send a tokenized string
+literal as an integer instead of a string argument, but this is not yet
+supported.
+
+A string token could be sent by marking an integer % argument in a way
+recognized by the detokenization tools. The detokenizer would expand the
+argument to the string represented by the integer.
+
+.. code-block:: cpp
+
+  #define PW_TOKEN_ARG "TOKEN<([\\%" PRIx32 "/])>END_TOKEN"
+
+  constexpr uint32_t answer_token = PW_TOKENIZE_STRING("Uh, who is there");
+
+  PW_TOKENIZE_TO_GLOBAL_HANDLER("Knock knock: " PW_TOKEN_ARG "?", answer_token);
+
+Strings with arguments could be encoded to a buffer, but since printf strings
+are null-terminated, a binary encoding would not work. These strings can be
+prefixed Base64-encoded and sent as ``%s`` instead. See `Base64 format`_.
+
+Another possibility: encode strings with arguments to a ``uint64_t`` and send
+them as an integer. This would be efficient and simple, but only support a small
+number of arguments.
+
+Compatibility
+=============
+  * C11
+  * C++17
+  * Python 3
+
+Dependencies
+============
+  * pw_varint module
+  * pw_preprocessor module
+  * pw_span module
diff --git a/pw_tokenizer/generate_decoding_test_data.cc b/pw_tokenizer/generate_decoding_test_data.cc
new file mode 100644
index 0000000..bc9c595
--- /dev/null
+++ b/pw_tokenizer/generate_decoding_test_data.cc
@@ -0,0 +1,471 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 program generates Python test data for decoder_test.py.
+//
+// To generate the test data, build the target
+// pw_tokenizer_generate_decoding_test_data. Execute the binary and move the
+// generated files to this directory.
+
+#include <array>
+#include <cctype>
+#include <cinttypes>
+#include <cstdarg>
+#include <cstdint>
+#include <cstdio>
+#include <random>
+
+#include "pw_span/span.h"
+#include "pw_tokenizer/internal/decode.h"
+#include "pw_tokenizer/tokenize.h"
+#include "pw_varint/varint.h"
+
+namespace {
+
+// Defines how to format test cases for the target language.
+struct SourceFileFormat {
+  const char* extension;
+  const char* comment;
+  const char* header;
+  const char* footer;
+  const char* test_case_prefix;
+  const char* binary_string_prefix;
+  const char* binary_string_suffix;
+};
+
+// clang-format off
+constexpr const char* kCopyrightLines[] = {
+"Copyright 2020 The Pigweed Authors",
+"",
+"Licensed under the Apache License, Version 2.0 (the \"License\"); you may not",
+"use this file except in compliance with the License. You may obtain a copy of",
+"the License at",
+"",
+"    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.",
+};
+// clang-format on
+
+// The header includes a %s for the name and a %s for the test case type.
+constexpr const char kCcHeader[] = R"(#pragma once
+
+#include <string_view>
+#include <tuple>
+
+namespace pw::test::%s {
+
+using namespace std::literals::string_view_literals;
+
+// clang-format off
+using TestCase = %s;
+
+inline constexpr TestCase kTestData[] = {
+)";
+
+constexpr const char kCcFooter[] = R"(
+};
+
+}  // namespace pw::test::%s
+)";
+
+constexpr const char kPythonHeader[] = R"("""Generated test data."""
+
+# pylint: disable=line-too-long
+# C++ test case type for %s:
+#     %s
+
+
+def TestCase(*args):  # pylint: disable=invalid-name
+    return tuple(args)
+
+
+# yapf: disable
+TEST_DATA = (
+)";
+
+constexpr SourceFileFormat kCcFormat{
+    ".h", "//", kCcHeader, kCcFooter, "TestCase", "\"", "\"sv"};
+
+constexpr SourceFileFormat kPythonFormat{
+    ".py", "#", kPythonHeader, "\n)\n", "", "b'", "'"};
+
+class TestDataFile {
+ public:
+  TestDataFile(const char* name,
+               const SourceFileFormat& format,
+               const char* test_case_format)
+      : format_(format),
+        name_(name),
+        test_case_format_(test_case_format),
+        path_(std::string(name) + "_test_data" + format_.extension),
+        file_(std::fopen(path_.c_str(), "w")) {}
+
+  ~TestDataFile() { std::fclose(file_); }
+
+  const SourceFileFormat& fmt() const { return format_; }
+  const std::string& path() const { return path_; }
+
+  // Writes a file with test cases uses the provided function.
+  void WriteTestCases(void (*function)(TestDataFile*)) {
+    static constexpr const char* kFileBase =
+        &__FILE__[std::string_view(__FILE__).find_last_of('/') + 1];
+
+    for (const char* line : kCopyrightLines) {
+      printf("%s", fmt().comment);
+      if (line[0] == '\0') {
+        printf("\n");
+      } else {
+        printf(" %s\n", line);
+      }
+    }
+
+    printf("\n%s AUTOGENERATED - DO NOT EDIT\n", fmt().comment);
+    printf("%s This file contains test data generated by %s.\n",
+           fmt().comment,
+           kFileBase);
+
+    printf(fmt().header, name_, test_case_format_);
+    function(this);
+    printf(fmt().footer, name_);
+  }
+
+  // Starts a section of test cases in the file.
+  void Section(const char* comment) {
+    printf("\n%s %s\n", fmt().comment, comment);
+  }
+
+  int printf(const char* format, ...) PW_PRINTF_FORMAT(2, 3) {
+    va_list args;
+    va_start(args, format);
+    const int result = std::vfprintf(file_, format, args);
+    va_end(args);
+    return result;
+  }
+
+ private:
+  SourceFileFormat format_;
+  const char* name_;
+  const char* test_case_format_;
+  std::string path_;
+  FILE* file_;
+};
+
+// Writes a decoding test case to the file.
+void TestCase(TestDataFile* file,
+              pw::span<const uint8_t> buffer,
+              const char* format,
+              const char* formatted) {
+  file->printf(R"(TestCase("%s", "%s", %s)",
+               format,
+               formatted,
+               file->fmt().binary_string_prefix);
+
+  for (uint8_t byte : buffer) {
+    file->printf("\\x%02x", byte);
+  }
+
+  file->printf("%s),\n", file->fmt().binary_string_suffix);
+}
+
+template <size_t kSize>
+void TestCase(TestDataFile* file,
+              const char* format,
+              const char (&buffer)[kSize],
+              const char* formatted) {
+  TestCase(file,
+           pw::span(reinterpret_cast<const uint8_t*>(buffer), kSize - 1),
+           format,
+           formatted);
+}
+
+// __VA_ARGS__ is expanded twice, so ONLY variables / constants should be used.
+#define MAKE_TEST_CASE(format, ...)                                           \
+  do {                                                                        \
+    std::array<uint8_t, 128> buffer;                                          \
+    size_t size = buffer.size();                                              \
+    PW_TOKENIZE_TO_BUFFER(buffer.data(), &size, format, ##__VA_ARGS__);       \
+                                                                              \
+    std::array<char, 128> formatted = {};                                     \
+    std::snprintf(formatted.data(), formatted.size(), format, ##__VA_ARGS__); \
+    TestCase(file,                                                            \
+             pw::span(buffer).first(size).subspan(4), /* skip the token */    \
+             format,                                                          \
+             formatted.data());                                               \
+  } while (0)
+
+// Formats the contents like an error.
+#define ERROR_STR PW_TOKENIZER_ARG_DECODING_ERROR
+
+extern "C" void pw_TokenizerHandleEncodedMessage(const uint8_t*, size_t) {}
+extern "C" void pw_TokenizerHandleEncodedMessageWithPayload(pw_TokenizerPayload,
+                                                            const uint8_t*,
+                                                            size_t) {}
+
+// Generates data to test tokenized string decoding.
+void GenerateEncodedStrings(TestDataFile* file) {
+  std::mt19937 random(6006411);
+  std::uniform_int_distribution<int64_t> big;
+  std::uniform_int_distribution<int32_t> medium;
+  std::uniform_int_distribution<char> small(' ', '~');
+  std::uniform_real_distribution<float> real;
+
+  file->Section("Simple strings");
+  TestCase(file, "%s", "\3SFO", "SFO");
+  TestCase(file, "%s", "\4KSJC", "KSJC");
+  TestCase(file, "%s", "\0", "");
+
+  TestCase(file, "%5s%s", "\2no\3fun", "   nofun");
+  TestCase(file, "%5s%s", "\6abcdef\0", "abcdef");
+  TestCase(file, "%5s%s", "\0\6abcdef", "     abcdef");
+
+  TestCase(file,
+           "%s %-6s%s%s%s",
+           "\5Intel\580586\7toaster\1 \4oven",
+           "Intel 80586 toaster oven");
+  TestCase(file,
+           "%s %-6s%s%s%s",
+           "\5Apple\x09"
+           "automatic\7 pencil\1 \x09sharpener",
+           "Apple automatic pencil sharpener");
+
+  file->Section("Zero-length strings");
+  TestCase(file, "%s-%s", "\x02so\x00", "so-");
+  TestCase(file, "%s-%s", "\x00\04cool", "-cool");
+  TestCase(file, "%s%s%3s%s", "\0\0\0\0", "   ");
+  TestCase(file, "(%5s)(%2s)(%7s)", "\x80\0\x80", "([...])(  )(  [...])");
+
+  file->Section("Invalid strings");
+  TestCase(file, "%s", "\x03hi", ERROR_STR("%s ERROR (hi)"));
+  TestCase(file, "%30s", "\x03hi", ERROR_STR("%30s ERROR (hi)"));
+  TestCase(file, "%30s", "\x83hi", ERROR_STR("%30s ERROR (hi)"));
+  TestCase(file, "%s", "\x85yo!", ERROR_STR("%s ERROR (yo!)"));
+  TestCase(file, "%s", "\x01", ERROR_STR("%s ERROR"));
+  TestCase(file, "%30s", "\x81", ERROR_STR("%30s ERROR"));
+
+  file->Section("Continue after truncated string");
+  TestCase(file, "%s %d %s", "\x82go\4\5lunch", "go[...] 2 lunch");
+  TestCase(file, "%6s%s%s", "\x80\x85hello\x05there", " [...]hello[...]there");
+
+  file->Section("Floating point");
+  TestCase(file, "%1.1f", "\0\0\0\0", "0.0");
+  TestCase(file, "%0.5f", "\xdb\x0f\x49\x40", "3.14159");
+
+  file->Section("Character");  // ZigZag doubles the value of positive integers.
+  TestCase(file, "%c", "\x40", " ");          // 0x20
+  TestCase(file, "%c", "\x48", "$");          // 0x24
+  TestCase(file, "%c", "\x48", "$");          // 0x24
+  TestCase(file, "100%c!", "\x4A", "100%!");  // 0x25
+
+  file->Section("Atypical argument types");
+  MAKE_TEST_CASE("%ju", static_cast<uintmax_t>(99));
+  MAKE_TEST_CASE("%jd", static_cast<intmax_t>(99));
+  MAKE_TEST_CASE("%zu", sizeof(uint64_t));
+  MAKE_TEST_CASE("%zd", static_cast<ssize_t>(123));
+  MAKE_TEST_CASE("%td", static_cast<ptrdiff_t>(99));
+
+  file->Section("Percent character");
+  TestCase(file, "%%", "", "%");
+  TestCase(file, "%%%%%%%%", "abc", "%%%%");
+  TestCase(file, "whoa%%%%wow%%%%!%%", "", "whoa%%wow%%!%");
+  TestCase(file, "This is %d%% effective", "\x02", "This is 1% effective");
+  TestCase(
+      file, "%% is 100%sa%%sign%%%s", "\x01%\x03OK?", "% is 100%a%sign%OK?");
+
+  file->Section("Percent character prints after errors");
+  TestCase(file, "%s%%", "\x83-10\0", "-10[...]%");
+  TestCase(
+      file, "%d%% is a good %%", "", ERROR_STR("%d MISSING") "% is a good %");
+
+  file->Section("Various format strings");
+  MAKE_TEST_CASE("!");
+  MAKE_TEST_CASE("%s", "%s");
+  MAKE_TEST_CASE("%s", "hello");
+  MAKE_TEST_CASE("%s%s", "Hello", "old");
+  MAKE_TEST_CASE("%s to the%c%s", "hello", ' ', "whirled");
+  MAKE_TEST_CASE("hello %s %d %d %d", "rolled", 1, 2, 3);
+
+  TestCase(file, "", "", "");
+  TestCase(file, "This has no specifiers", "", "This has no specifiers");
+  TestCase(file, "%s_or_%3s", "\x05hello\x02hi", "hello_or_ hi");
+  TestCase(file, "%s_or_%3d", "\x05hello\x7f", "hello_or_-64");
+  TestCase(file,
+           "%s or hi%c pi=%1.2e",
+           "\x05hello\x42\xdb\x0f\x49\x40",
+           "hello or hi! pi=3.14e+00");
+  TestCase(file,
+           "Why, %s there. My favorite number is %.2f%c",
+           "\x05hello\xdb\x0f\x49\x40\x42",
+           "Why, hello there. My favorite number is 3.14!");
+
+  file->Section("Various errors");
+  TestCase(file, "%d", "", ERROR_STR("%d MISSING"));
+
+  TestCase(file,
+           "ABC%d123%dabc%dABC",
+           "",
+           "ABC" ERROR_STR("%d MISSING") "123" ERROR_STR(
+               "%d SKIPPED") "abc" ERROR_STR("%d SKIPPED") "ABC");
+
+  TestCase(file,
+           "%sXY%+ldxy%a",
+           "\x83Yo!\x80",
+           "Yo![...]XY" ERROR_STR("%+ld ERROR") "xy" ERROR_STR("%a SKIPPED"));
+
+  TestCase(file, "%d", "", ERROR_STR("%d MISSING"));
+
+  TestCase(file,
+           "%sXY%+ldxy%a",
+           "\x83Yo!\x80",
+           "Yo![...]XY" ERROR_STR("%+ld ERROR") "xy" ERROR_STR("%a SKIPPED"));
+
+  TestCase(file,
+           "%s%lld%9u",
+           "\x81$\x80\x80",
+           "$[...]" ERROR_STR("%lld ERROR") ERROR_STR("%9u SKIPPED"));
+
+  file->Section("Alternate form (#)");
+  MAKE_TEST_CASE("Hex: %#x", 0xbeef);
+  MAKE_TEST_CASE("Hex: %#08X", 0xfeed);
+
+  file->Section("Random integers");
+  for (int i = 0; i < 100; ++i) {
+    float f = real(random);
+    MAKE_TEST_CASE(
+        "This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", f, f, i, f, f, f, f);
+  }
+
+  for (int i = 0; i < 100; ++i) {
+    unsigned long long n1 = big(random);
+    int n2 = medium(random);
+    char ch = small(random);
+    if (ch == '"' || ch == '\\') {
+      ch = '\t';
+    }
+
+    MAKE_TEST_CASE("%s: %llu %d %c", std::to_string(i).c_str(), n1, n2, ch);
+  }
+
+  for (int i = 0; i < 100; ++i) {
+    const long long n1 = big(random);
+    const unsigned n2 = medium(random);
+    const char ch = small(random);
+
+    MAKE_TEST_CASE(
+        "%s: %lld 0x%16u%08X %d", std::to_string(i).c_str(), n1, n2, n2, ch);
+  }
+}
+
+template <typename T>
+void OutputVarintTest(TestDataFile* file, T i) {
+  if constexpr (sizeof(T) <= sizeof(int)) {
+    file->printf(R"(TestCase("%%d", "%d", "%%u", "%u", %s)",
+                 static_cast<int>(i),
+                 static_cast<unsigned>(i),
+                 file->fmt().binary_string_prefix);
+  } else {
+    file->printf(R"(TestCase("%%lld", "%lld", "%%llu", "%llu", %s)",
+                 static_cast<long long>(i),
+                 static_cast<unsigned long long>(i),
+                 file->fmt().binary_string_prefix);
+  }
+
+  std::array<uint8_t, 10> buffer;
+  // All integers are encoded as signed for tokenization.
+  size_t size = pw::varint::Encode(i, pw::as_writable_bytes(pw::span(buffer)));
+
+  for (size_t i = 0; i < size; ++i) {
+    file->printf("\\x%02x", buffer[i]);
+  }
+
+  file->printf("%s),\n", file->fmt().binary_string_suffix);
+}
+
+// Generates data to test variable-length integer decoding.
+void GenerateVarints(TestDataFile* file) {
+  std::mt19937 random(6006411);
+  std::uniform_int_distribution<int64_t> signed64;
+  std::uniform_int_distribution<int32_t> signed32;
+  std::uniform_int_distribution<int16_t> signed16;
+
+  file->Section("Important numbers");
+  OutputVarintTest(file, 0);
+  OutputVarintTest(file, std::numeric_limits<int16_t>::min());
+  OutputVarintTest(file, std::numeric_limits<int16_t>::min() + 1);
+  OutputVarintTest(file, std::numeric_limits<int16_t>::max() - 1);
+  OutputVarintTest(file, std::numeric_limits<int16_t>::max());
+  OutputVarintTest(file, std::numeric_limits<int32_t>::min());
+  OutputVarintTest(file, std::numeric_limits<int32_t>::min() + 1);
+  OutputVarintTest(file, std::numeric_limits<int32_t>::max() - 1);
+  OutputVarintTest(file, std::numeric_limits<int32_t>::max());
+  OutputVarintTest(file, std::numeric_limits<int64_t>::min());
+  OutputVarintTest(file, std::numeric_limits<int64_t>::min() + 1);
+  OutputVarintTest(file, std::numeric_limits<int64_t>::max() - 1);
+  OutputVarintTest(file, std::numeric_limits<int64_t>::max());
+
+  file->Section("Random 64-bit ints");
+  for (int i = 0; i < 500; ++i) {
+    OutputVarintTest(file, signed64(random));
+  }
+  file->Section("Random 32-bit ints");
+  for (int i = 0; i < 100; ++i) {
+    OutputVarintTest(file, signed32(random));
+  }
+  file->Section("Random 16-bit ints");
+  for (int i = 0; i < 100; ++i) {
+    OutputVarintTest(file, signed16(random));
+  }
+
+  file->Section("All 8-bit numbers");
+  {
+    int i = std::numeric_limits<int8_t>::min();
+    while (true) {
+      OutputVarintTest(file, i);
+      if (i == std::numeric_limits<int8_t>::max()) {
+        break;
+      }
+      // Don't use an inline increment to avoid undefined behavior (overflow).
+      i += 1;
+    }
+  }
+}
+
+template <typename Function>
+void WriteFile(const char* name,
+               const char* test_case_format,
+               Function function) {
+  for (const SourceFileFormat& file_format : {kCcFormat, kPythonFormat}) {
+    TestDataFile file(name, file_format, test_case_format);
+    file.WriteTestCases(function);
+
+    std::printf("Wrote %s\n", file.path().c_str());
+  }
+}
+
+}  // namespace
+
+int main(int, char**) {
+  WriteFile("tokenized_string_decoding",
+            "std::tuple<const char*, std::string_view, std::string_view>",
+            GenerateEncodedStrings);
+  WriteFile("varint_decoding",
+            "std::tuple<const char*, const char*, const char*, const char*, "
+            "std::string_view>",
+            GenerateVarints);
+  return 0;
+}
diff --git a/pw_tokenizer/hash_test.cc b/pw_tokenizer/hash_test.cc
new file mode 100644
index 0000000..8392437
--- /dev/null
+++ b/pw_tokenizer/hash_test.cc
@@ -0,0 +1,240 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 <cstddef>
+#include <cstdint>
+
+#include "gtest/gtest.h"
+#include "pw_preprocessor/util.h"
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h"
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h"
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h"
+#include "pw_tokenizer/pw_tokenizer_65599_fixed_length_hash.h"
+#include "pw_tokenizer_private/generated_hash_test_cases.h"
+
+namespace pw::tokenizer {
+namespace {
+
+// Make sure the array test cases file isn't empty.
+static_assert(PW_ARRAY_SIZE(kHashTests) > 10,
+              "The test cases array is suspiciously small");
+
+constexpr bool CheckGeneratedCases() {
+  for (const auto [string, hash_length, python_hash, macro_hash] : kHashTests) {
+    const uint32_t calculated_hash =
+        PwTokenizer65599FixedLengthHash(string, hash_length);
+    if (calculated_hash != python_hash || calculated_hash != macro_hash) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static_assert(CheckGeneratedCases(),
+              "Hashes in the generated test cases must match");
+
+TEST(Hashing, GeneratedCasesAtRuntime) {
+  for (const auto [string, hash_length, python_hash, macro_hash] : kHashTests) {
+    const uint32_t calculated_hash =
+        PwTokenizer65599FixedLengthHash(string, hash_length);
+    EXPECT_EQ(calculated_hash, python_hash);
+    EXPECT_EQ(calculated_hash, macro_hash);
+  }
+}
+
+// Gets the size of the string, excluding the null terminator. A uint32_t is
+// used instead of a size_t since the hash calculation requires a uint32_t.
+template <uint32_t kSizeIncludingNull>
+constexpr uint32_t StringLength(const char (&)[kSizeIncludingNull]) {
+  static_assert(kSizeIncludingNull > 0u);
+  return kSizeIncludingNull - 1;  // subtract the null terminator
+}
+
+TEST(Hashing, Runtime) {
+  // Coefficients for the hash terms; k1 is 1.
+  static constexpr uint32_t k2 = k65599HashConstant;
+  static constexpr uint32_t k3 = k65599HashConstant * k2;
+  static constexpr uint32_t k4 = k65599HashConstant * k3;
+  static constexpr uint32_t k5 = k65599HashConstant * k4;
+
+  // Hash a few things at hash length 4
+  EXPECT_EQ(PwTokenizer65599FixedLengthHash("", 4), StringLength(""));
+  EXPECT_EQ(PwTokenizer65599FixedLengthHash("1", 4),
+            StringLength("1") + k2 * '1');
+  EXPECT_EQ(PwTokenizer65599FixedLengthHash("12", 4),
+            StringLength("12") + k2 * '1' + k3 * '2');
+  EXPECT_EQ(PwTokenizer65599FixedLengthHash("123", 4),
+            StringLength("123") + k2 * '1' + k3 * '2' + k4 * '3');
+  EXPECT_EQ(PwTokenizer65599FixedLengthHash("1234", 4),
+            StringLength("1234") + k2 * '1' + k3 * '2' + k4 * '3' + k5 * '4');
+  EXPECT_EQ(PwTokenizer65599FixedLengthHash("12345", 4),
+            StringLength("12345") + k2 * '1' + k3 * '2' + k4 * '3' + k5 * '4');
+  EXPECT_EQ(PwTokenizer65599FixedLengthHash("123456", 4),
+            StringLength("123456") + k2 * '1' + k3 * '2' + k4 * '3' + k5 * '4');
+}
+
+// Use std::string_view so that \0 can appear in strings.
+#define TEST_SUPPORTED_HASHES(string_literal)                                  \
+  static_assert(                                                               \
+      PwTokenizer65599FixedLengthHash(                                         \
+          std::string_view(string_literal, sizeof(string_literal) - 1), 80) == \
+          PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH(string_literal),             \
+      "80-byte hash mismatch!");                                               \
+  static_assert(                                                               \
+      PwTokenizer65599FixedLengthHash(                                         \
+          std::string_view(string_literal, sizeof(string_literal) - 1), 96) == \
+          PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH(string_literal),             \
+      "96-byte hash mismatch!");                                               \
+  static_assert(                                                               \
+      PwTokenizer65599FixedLengthHash(                                         \
+          std::string_view(string_literal, sizeof(string_literal) - 1),        \
+          128) == PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH(string_literal),    \
+      "128-byte hash mismatch!")
+
+TEST(HashMacro, Empty) { TEST_SUPPORTED_HASHES(""); }
+
+TEST(HashMacro, NullTerminatorsAreIncludedInHash) {
+  using namespace std::literals;  // use an sv literal to include the \0
+
+  TEST_SUPPORTED_HASHES("hello\0there");
+  TEST_SUPPORTED_HASHES("\0");
+  TEST_SUPPORTED_HASHES("\0\0");
+  TEST_SUPPORTED_HASHES("\0\0\0");
+
+  static_assert(
+      PwTokenizer65599FixedLengthHash(""sv, 80) !=
+          PwTokenizer65599FixedLengthHash("\0"sv, 80),
+      "Hashes of \\0 strings should differ since the lengths are included");
+  static_assert(
+      PwTokenizer65599FixedLengthHash("\0"sv, 80) !=
+          PwTokenizer65599FixedLengthHash("\0\0"sv, 80),
+      "Hashes of \\0 strings should differ since the lengths are included");
+  static_assert(
+      PwTokenizer65599FixedLengthHash("\0\0"sv, 80) !=
+          PwTokenizer65599FixedLengthHash("\0\0\0"sv, 80),
+      "Hashes of \\0 strings should differ since the lengths are included");
+}
+
+TEST(HashMacro, OneChar) {
+  TEST_SUPPORTED_HASHES("a");
+  TEST_SUPPORTED_HASHES("c");
+  TEST_SUPPORTED_HASHES("A");
+  TEST_SUPPORTED_HASHES("B");
+  TEST_SUPPORTED_HASHES("C");
+  TEST_SUPPORTED_HASHES("$");
+  TEST_SUPPORTED_HASHES("%");
+  TEST_SUPPORTED_HASHES("^");
+  TEST_SUPPORTED_HASHES("\xa1");
+  TEST_SUPPORTED_HASHES("\xff");
+}
+
+TEST(HashMacro, Phrases) {
+  TEST_SUPPORTED_HASHES("WASD");
+  TEST_SUPPORTED_HASHES("hello, world");
+  TEST_SUPPORTED_HASHES("Let the wookie win.");
+  TEST_SUPPORTED_HASHES("\x01 more test, just for \xffun");
+}
+
+TEST(HashMacro, HashesChange) {
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("") !=
+                PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\0"));
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("a") !=
+                PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("b"));
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("z") !=
+                PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("aa"));
+  static_assert(
+      PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("make sure hashes change") !=
+      PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("MAKE SURE HASHES CHANGE"));
+}
+
+// Define string literals to test boundary conditions.
+#define LITERAL_79   \
+  "0123456789ABCDEF" \
+  "0123456789ABCDEF" \
+  "0123456789ABCDEF" \
+  "0123456789ABCDEF" \
+  "0123456789ABCDE"
+#define LITERAL_80 LITERAL_79 "F"
+
+#define LITERAL_96 LITERAL_80 "0123456789ABCDEF"
+
+#define LITERAL_128  \
+  LITERAL_96         \
+  "0123456789ABCDEF" \
+  "0123456789ABCDEF"
+
+static_assert(sizeof(LITERAL_79) - 1 == 79);
+static_assert(sizeof(LITERAL_80) - 1 == 80);
+static_assert(sizeof(LITERAL_96) - 1 == 96);
+static_assert(sizeof(LITERAL_128) - 1 == 128);
+
+TEST(HashMacro, HashNearFixedHashLength_80) {
+  // 79, 80, 81, 82 bytes
+  TEST_SUPPORTED_HASHES(LITERAL_79);
+  TEST_SUPPORTED_HASHES(LITERAL_80);
+  TEST_SUPPORTED_HASHES(LITERAL_80 "!");
+  TEST_SUPPORTED_HASHES(LITERAL_80 "!!");
+}
+
+TEST(HashMacro, HashNearFixedHashLength_96) {
+  // 95, 96, 97, 98 bytes
+  TEST_SUPPORTED_HASHES(LITERAL_80 "0123456789ABCDE");
+  TEST_SUPPORTED_HASHES(LITERAL_96);
+  TEST_SUPPORTED_HASHES(LITERAL_96 "!");
+  TEST_SUPPORTED_HASHES(LITERAL_96 "XY");
+}
+
+TEST(HashMacro, HashNearFixedHashLength_128) {
+  // 127, 128, 129, 130 bytes
+  TEST_SUPPORTED_HASHES(LITERAL_96
+                        "0123456789ABCDEF"
+                        "0123456789ABCDE");
+  TEST_SUPPORTED_HASHES(LITERAL_128);
+  TEST_SUPPORTED_HASHES(LITERAL_128 "!");
+  TEST_SUPPORTED_HASHES(LITERAL_128 "AB");
+}
+
+TEST(HashMacro, HashVeryLongStrings) {
+  TEST_SUPPORTED_HASHES(
+      "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+      "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+      "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+      "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+      "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+  TEST_SUPPORTED_HASHES(LITERAL_128 LITERAL_128 LITERAL_128);
+}
+
+TEST(HashMacro, ExtraCharactersAreIgnored) {
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH(LITERAL_80 "?") ==
+                PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH(LITERAL_80 "."));
+
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH(LITERAL_80 "ABCD") ==
+                PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH(LITERAL_80 "zyxw"));
+
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH(LITERAL_96 "?") ==
+                PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH(LITERAL_96 "."));
+
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH(LITERAL_96 "ABCD") ==
+                PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH(LITERAL_96 "zyxw"));
+
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH(LITERAL_128 "?") ==
+                PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH(LITERAL_128 "."));
+
+  static_assert(PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH(LITERAL_128 "ABCD") ==
+                PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH(LITERAL_128 "zyxw"));
+}
+
+}  // namespace
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/java/dev/pigweed/tokenizer/Detokenizer.java b/pw_tokenizer/java/dev/pigweed/tokenizer/Detokenizer.java
new file mode 100644
index 0000000..01a21e1
--- /dev/null
+++ b/pw_tokenizer/java/dev/pigweed/tokenizer/Detokenizer.java
@@ -0,0 +1,91 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 is a Java Native Interface (JNI) wrapper for the Detokenizer class.
+ *
+ * <p>This classes uses the Android Base64 library instead of the standard Java Base64, which is not
+ * yet available on Android. To use this class outside of Android, replace android.util.Base64 with
+ * java.util.Base64.
+ */
+package dev.pigweed.tokenizer;
+
+import android.util.Base64;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** This class provides the Java interface for the C++ Detokenizer class. */
+public class Detokenizer {
+  // Android's Base64 library doesn't seem to check if the Base64-encoded data is valid. This
+  // regular expression checks that it is. Does not match URL-safe or unpadded Base64.
+  private static final Pattern TOKENIZED_STRING =
+      Pattern.compile("\\$([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?");
+
+  static {
+    System.loadLibrary("detokenizer");
+  }
+
+  // The handle to the C++ detokenizer instance.
+  private final long handle;
+
+  public Detokenizer() {
+    this(new byte[0]);
+  }
+
+  public Detokenizer(byte[] tokenDatabase) {
+    handle = newNativeDetokenizer(tokenDatabase);
+  }
+
+  /**
+   * Detokenizes and replaces all recognized tokenized messages ($ followed by Base64) in the
+   * provided string. Unrecognized tokenized strings are left unchanged.
+   */
+  public String detokenize(String message) {
+    Matcher matcher = TOKENIZED_STRING.matcher(message);
+    StringBuilder result = new StringBuilder();
+    int lastIndex = 0;
+
+    while (matcher.find()) {
+      result.append(message, lastIndex, matcher.start());
+
+      String decoded =
+          detokenizeNative(handle, Base64.decode(matcher.group().substring(1), Base64.DEFAULT));
+      result.append(decoded != null ? decoded : matcher.group());
+
+      lastIndex = matcher.end();
+    }
+
+    result.append(message, lastIndex, message.length());
+    return result.toString();
+  }
+
+  /** Deletes memory allocated in C++ when this class is garbage collected. */
+  @Override
+  protected void finalize() {
+    deleteNativeDetokenizer(handle);
+  }
+
+  /** Creates a new detokenizer using the provided data as the database. */
+  private static native long newNativeDetokenizer(byte[] data);
+
+  /** Deletes the detokenizer object with the provided handle, which MUST be valid. */
+  private static native void deleteNativeDetokenizer(long handle);
+
+  /**
+   * Returns the detokenized version of the provided data. This is non-static so this object has a
+   * reference held while the function is running, which prevents finalize from running before
+   * detokenizeNative finishes.
+   */
+  private native String detokenizeNative(long handle, byte[] data);
+}
diff --git a/pw_tokenizer/java/dev/pigweed/tokenizer/detokenizer.cc b/pw_tokenizer/java/dev/pigweed/tokenizer/detokenizer.cc
new file mode 100644
index 0000000..b2c8f33
--- /dev/null
+++ b/pw_tokenizer/java/dev/pigweed/tokenizer/detokenizer.cc
@@ -0,0 +1,92 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 provides a Java Native Interface (JNI) version of the Detokenizer
+// class. This facilitates using the tokenizer library from Java or other JVM
+// languages. A corresponding Java class is provided in Detokenizer.java.
+
+#include <jni.h>
+
+#include <cstring>
+
+#include "pw_preprocessor/concat.h"
+#include "pw_span/span.h"
+#include "pw_tokenizer/detokenize.h"
+#include "pw_tokenizer/token_database.h"
+
+#define DETOKENIZER_METHOD(method) \
+  JNICALL PW_CONCAT(Java_dev_pigweed_tokenizer_, Detokenizer_, method)
+
+namespace pw::tokenizer {
+namespace {
+
+Detokenizer* HandleToPointer(jlong handle) {
+  Detokenizer* detokenizer = nullptr;
+  std::memcpy(&detokenizer, &handle, sizeof(detokenizer));
+  static_assert(sizeof(detokenizer) <= sizeof(handle));
+  return detokenizer;
+}
+
+jlong PointerToHandle(Detokenizer* detokenizer) {
+  jlong handle = 0;
+  std::memcpy(&handle, &detokenizer, sizeof(detokenizer));
+  static_assert(sizeof(handle) >= sizeof(detokenizer));
+  return handle;
+}
+
+}  // namespace
+
+extern "C" {
+
+static_assert(sizeof(jbyte) == 1u);
+
+JNIEXPORT jlong DETOKENIZER_METHOD(newNativeDetokenizer)(JNIEnv* env,
+                                                         jclass,
+                                                         jbyteArray array) {
+  jbyte* const data = env->GetByteArrayElements(array, nullptr);
+  const jsize size = env->GetArrayLength(array);
+
+  TokenDatabase tokens = TokenDatabase::Create(pw::span(data, size));
+  const jlong handle =
+      PointerToHandle(new Detokenizer(tokens.ok() ? tokens : TokenDatabase()));
+
+  env->ReleaseByteArrayElements(array, data, 0);
+  return handle;
+}
+
+JNIEXPORT void DETOKENIZER_METHOD(deleteNativeDetokenizer)(JNIEnv*,
+                                                           jclass,
+                                                           jlong handle) {
+  delete HandleToPointer(handle);
+}
+
+JNIEXPORT jstring DETOKENIZER_METHOD(detokenizeNative)(JNIEnv* env,
+                                                       jobject,
+                                                       jlong handle,
+                                                       jbyteArray array) {
+  jbyte* const data = env->GetByteArrayElements(array, nullptr);
+  const jsize size = env->GetArrayLength(array);
+
+  DetokenizedString result = HandleToPointer(handle)->Detokenize(data, size);
+
+  env->ReleaseByteArrayElements(array, data, 0);
+
+  return result.matches().empty()
+             ? nullptr
+             : env->NewStringUTF(result.BestString().c_str());
+}
+
+}  // extern "C"
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/public/pw_tokenizer/config.h b/pw_tokenizer/public/pw_tokenizer/config.h
new file mode 100644
index 0000000..d9798d0
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/config.h
@@ -0,0 +1,88 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// Configuration macros for the tokenizer module.
+#pragma once
+
+#include <assert.h>
+
+// TODO(pwbug/17): Configure these options in the config system.
+
+// For a tokenized string that has arguments, the types of the arguments are
+// encoded in either a 4-byte (uint32_t) or a 8-byte (uint64_t) value. The 4 or
+// 6 least-significant bits, respectively, store the number of arguments, while
+// the remaining bits encode the argument types. Argument types are encoded
+// two-bits per argument, in little-endian order. Up to 14 arguments in 4 bytes
+// or 29 arguments in 8 bytes are supported.
+#ifndef PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES
+#define PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES 4
+#endif  // PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES
+
+static_assert(PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES == 4 ||
+                  PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES == 8,
+              "PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES must be 4 or 8");
+
+// How long of a string to hash. Strings shorter than this length are treated as
+// if they were zero-padded up to the length. Strings that are the same length
+// and share a common prefix longer than this value hash to the same value.
+//
+// Increasing PW_TOKENIZER_CFG_HASH_LENGTH increases the compilation time for C
+// due to the complexity of the hashing macros. C++ macros use a constexpr
+// function instead of a macro, so the compilation time impact is minimal.
+// Projects primarily in C++ should use a large value for
+// PW_TOKENIZER_CFG_HASH_LENGTH (perhaps even
+// std::numeric_limits<size_t>::max()).
+//
+// Only hash lengths for which there is a corresponding macro header
+// (pw_tokenizer/internal/mash_macro_#.h) are supported. Additional macros may
+// be generated with the generate_hash_macro.py function. New macro headers must
+// then be added to pw_tokenizer/internal/hash.h.
+#ifndef PW_TOKENIZER_CFG_HASH_LENGTH
+#define PW_TOKENIZER_CFG_HASH_LENGTH 128
+#endif  // PW_TOKENIZER_CFG_HASH_LENGTH
+
+// The size of the stack-allocated argument encoding buffer to use. This only
+// affects tokenization macros that stack-allocate the encoding buffer
+// (PW_TOKENIZE_TO_CALLBACK and PW_TOKENIZE_TO_GLOBAL_HANDLER). This buffer size
+// is only allocated for argument encoding and does not include the 4-byte
+// token.
+//
+// This buffer does not need to be large to accommodate a good number of
+// tokenized string arguments. Integer arguments are usually encoded smaller
+// than their native size (e.g. 1 or 2 bytes for smaller numbers). All floating
+// point types are encoded as four bytes. Null-terminated strings are encoded
+// 1:1 in size.
+#ifndef PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES
+#define PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES 48
+#endif  // PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES
+
+// Enables the PW_TOKENIZE_TO_GLOBAL_HANDLER macro. If this option is enabled,
+// the pw_TokenizerHandleEncodedMessage function must be defined by the
+// application.
+#ifndef PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+#define PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER 1
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+// Enables the PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD macro. If this option
+// is enabled, the pw_TokenizerHandleEncodedMessageWithPayload function must be
+// defined by the application.
+#ifndef PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+#define PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD 1
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+
+// Sets the type of the payload argument to use for
+// PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD.
+#ifndef PW_TOKENIZER_CFG_PAYLOAD_TYPE
+#define PW_TOKENIZER_CFG_PAYLOAD_TYPE void*
+#endif  // PW_TOKENIZER_CFG_PAYLOAD_TYPE
diff --git a/pw_tokenizer/public/pw_tokenizer/detokenize.h b/pw_tokenizer/public/pw_tokenizer/detokenize.h
new file mode 100644
index 0000000..7450249
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/detokenize.h
@@ -0,0 +1,97 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 provides the Detokenizer class, which is used to decode tokenized
+// strings.  To use a Detokenizer, load a binary format token database into
+// memory, construct a TokenDatabase, and pass it to a Detokenizer:
+//
+//   std::vector data = ReadFile("my_tokenized_strings.db");
+//   Detokenizer detok(TokenDatabase::Create(data));
+//
+//   DetokenizedString result = detok.Detokenize(my_data);
+//   std::cout << result.BestString() << '\n';
+//
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "pw_span/span.h"
+#include "pw_tokenizer/internal/decode.h"
+#include "pw_tokenizer/token_database.h"
+
+namespace pw::tokenizer {
+
+using TokenizedStringEntry = std::pair<FormatString, uint32_t /*date removed*/>;
+
+// A string that has been detokenized. This class tracks all possible results if
+// there are token collisions.
+class DetokenizedString {
+ public:
+  DetokenizedString(uint32_t token,
+                    const span<const TokenizedStringEntry>& entries,
+                    const span<const uint8_t>& arguments);
+
+  DetokenizedString() : has_token_(false) {}
+
+  // True if there was only one valid match and it decoded successfully.
+  bool ok() const { return matches_.size() == 1 && matches_[0].ok(); }
+
+  // Returns the strings that matched the token, with the best matches first.
+  const std::vector<DecodedFormatString>& matches() const { return matches_; }
+
+  // Returns the detokenized string or an empty string if there were no matches.
+  // If there are multiple possible results, the DetokenizedString returns the
+  // first match.
+  std::string BestString() const;
+
+  // Returns the best match, with error messages inserted for arguments that
+  // failed to parse.
+  std::string BestStringWithErrors() const;
+
+ private:
+  uint32_t token_;
+  bool has_token_;
+  std::vector<DecodedFormatString> matches_;
+};
+
+// Decodes and detokenizes strings from a TokenDatabase. This class builds a
+// hash table from the TokenDatabase to give O(1) token lookups.
+class Detokenizer {
+ public:
+  // Constructs a detokenizer from a TokenDatabase. The TokenDatabase is not
+  // referenced by the Detokenizer after construction; its memory can be freed.
+  Detokenizer(const TokenDatabase& database);
+
+  // Decodes and detokenizes the encoded message. Returns a DetokenizedString
+  // that stores all possible detokenized string results.
+  DetokenizedString Detokenize(const span<const uint8_t>& encoded) const;
+
+  DetokenizedString Detokenize(const std::string_view& encoded) const {
+    return Detokenize(encoded.data(), encoded.size());
+  }
+
+  DetokenizedString Detokenize(const void* encoded, size_t size_bytes) const {
+    return Detokenize(span(static_cast<const uint8_t*>(encoded), size_bytes));
+  }
+
+ private:
+  std::unordered_map<uint32_t, std::vector<TokenizedStringEntry>> database_;
+};
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/argument_types.h b/pw_tokenizer/public/pw_tokenizer/internal/argument_types.h
new file mode 100644
index 0000000..4ceea40
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/internal/argument_types.h
@@ -0,0 +1,154 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 provides internal macros used by the tokenizer module.
+#pragma once
+
+#include <stdint.h>
+
+#include "pw_preprocessor/macro_arg_count.h"
+#include "pw_tokenizer/config.h"
+
+// The size of the argument types variable determines the number of arguments
+// supported in tokenized strings.
+#if PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES == 4
+
+#include "pw_tokenizer/internal/argument_types_macro_4_byte.h"
+
+// Encoding types in a uint32_t supports 14 arguments with 2 bits per argument.
+#define PW_TOKENIZER_MAX_SUPPORTED_ARGS 14
+#define PW_TOKENIZER_TYPE_COUNT_SIZE_BITS 4u
+#define PW_TOKENIZER_TYPE_COUNT_MASK 0x0Fu
+
+typedef uint32_t pw_TokenizerArgTypes;
+
+#elif PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES == 8
+
+#include "pw_tokenizer/internal/argument_types_macro_8_byte.h"
+
+// Encoding types in a uint64_t supports 29 arguments with 2 bits per argument.
+#define PW_TOKENIZER_MAX_SUPPORTED_ARGS 29
+#define PW_TOKENIZER_TYPE_COUNT_SIZE_BITS 6u
+#define PW_TOKENIZER_TYPE_COUNT_MASK 0x1Fu  // only 5 bits will be needed
+
+typedef uint64_t pw_TokenizerArgTypes;
+
+#else
+
+#error "Unsupported value for PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES"
+
+#endif  // PW_TOKENIZER_CFG_ARG_TYPES_SIZE_BYTES
+
+// The tokenized string encoding function is a variadic function that works
+// similarly to printf. Instead of a format string, however, the argument types
+// are packed into a pw_TokenizerArgTypes.
+//
+// The four supported argument types are represented by two-bit argument codes.
+// Just four types are required because only printf-compatible arguments are
+// supported, and variadic arguments are further converted to a more limited set
+// of types.
+//
+// char* values cannot be printed as pointers with %p. These arguments are
+// always encoded as strings. To format a char* as an address, cast it to void*
+// or an integer.
+#define PW_TOKENIZER_ARG_TYPE_INT ((pw_TokenizerArgTypes)0)
+#define PW_TOKENIZER_ARG_TYPE_INT64 ((pw_TokenizerArgTypes)1)
+#define PW_TOKENIZER_ARG_TYPE_DOUBLE ((pw_TokenizerArgTypes)2)
+#define PW_TOKENIZER_ARG_TYPE_STRING ((pw_TokenizerArgTypes)3)
+
+// Select the int argument type based on the size of the type. Values smaller
+// than int are promoted to int.
+#define _PW_TOKENIZER_SELECT_INT_TYPE(type)                \
+  (sizeof(type) <= sizeof(int) ? PW_TOKENIZER_ARG_TYPE_INT \
+                               : PW_TOKENIZER_ARG_TYPE_INT64)
+
+// The _PW_VARARGS_TYPE macro selects the varargs-promoted type at compile time.
+// The macro has to be different for C and C++ because C doesn't support
+// templates and C++ doesn't support _Generic.
+#ifdef __cplusplus
+
+#include <type_traits>
+
+#define _PW_VARARGS_TYPE(arg) ::pw::tokenizer::VarargsType<decltype(arg)>()
+
+namespace pw::tokenizer {
+
+// This function selects the matching type enum for supported argument types.
+template <typename T>
+constexpr pw_TokenizerArgTypes VarargsType() {
+  using ArgType = std::decay_t<T>;
+
+  if constexpr (std::is_floating_point<ArgType>()) {
+    return PW_TOKENIZER_ARG_TYPE_DOUBLE;
+  } else if constexpr (!std::is_null_pointer<ArgType>() &&
+                       std::is_convertible<ArgType, const char*>()) {
+    return PW_TOKENIZER_ARG_TYPE_STRING;
+  } else if constexpr (sizeof(ArgType) == sizeof(int64_t)) {
+    return PW_TOKENIZER_ARG_TYPE_INT64;
+  } else {
+    static_assert(sizeof(ArgType) <= sizeof(int));
+    return PW_TOKENIZER_ARG_TYPE_INT;
+  }
+}
+
+}  // namespace pw::tokenizer
+
+#else  // C version
+
+// This uses a C11 _Generic to select the matching enum value for each supported
+// argument type. _Generic evaluates to the expression matching the type of the
+// provided expression at compile time.
+// clang-format off
+#define _PW_VARARGS_TYPE(arg)                                            \
+  _Generic((arg),                                                        \
+               _Bool:  PW_TOKENIZER_ARG_TYPE_INT,                        \
+                char:  PW_TOKENIZER_ARG_TYPE_INT,                        \
+         signed char:  PW_TOKENIZER_ARG_TYPE_INT,                        \
+       unsigned char:  PW_TOKENIZER_ARG_TYPE_INT,                        \
+        signed short:  PW_TOKENIZER_ARG_TYPE_INT,                        \
+      unsigned short:  PW_TOKENIZER_ARG_TYPE_INT,                        \
+          signed int:  PW_TOKENIZER_ARG_TYPE_INT,                        \
+        unsigned int:  PW_TOKENIZER_ARG_TYPE_INT,                        \
+         signed long: _PW_TOKENIZER_SELECT_INT_TYPE(signed long),        \
+       unsigned long: _PW_TOKENIZER_SELECT_INT_TYPE(unsigned long),      \
+    signed long long: _PW_TOKENIZER_SELECT_INT_TYPE(signed long long),   \
+  unsigned long long: _PW_TOKENIZER_SELECT_INT_TYPE(unsigned long long), \
+               float:  PW_TOKENIZER_ARG_TYPE_DOUBLE,                     \
+              double:  PW_TOKENIZER_ARG_TYPE_DOUBLE,                     \
+         long double:  PW_TOKENIZER_ARG_TYPE_DOUBLE,                     \
+               char*:  PW_TOKENIZER_ARG_TYPE_STRING,                     \
+         const char*:  PW_TOKENIZER_ARG_TYPE_STRING,                     \
+             default: _PW_TOKENIZER_SELECT_INT_TYPE(void*))
+// clang-format on
+
+#endif  // __cplusplus
+
+// Encodes the types of the provided arguments as a pw_TokenizerArgTypes value.
+// Depending on the size of pw_TokenizerArgTypes, the bottom 4 or 6 bits store
+// the number of arguments and the remaining bits store the types, two bits per
+// type.
+//
+// The arguments are not evaluated; only their types are used to
+// select the set their corresponding PW_TOKENIZER_ARG_TYPEs.
+#define PW_TOKENIZER_ARG_TYPES(...) \
+  _PW_TOKENIZER_TYPES_N(PW_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)
+
+// Selects which _PW_TOKENIZER_TYPES_* macro to use based on the number of
+// arguments this was called with.
+#define _PW_TOKENIZER_TYPES_N(count, ...) \
+  _PW_TOKENIZER_TYPES_EXPAND_N(count, __VA_ARGS__)
+#define _PW_TOKENIZER_TYPES_EXPAND_N(count, ...) \
+  _PW_TOKENIZER_TYPES_##count(__VA_ARGS__)
+
+#define _PW_TOKENIZER_TYPES_0() ((pw_TokenizerArgTypes)0)
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/argument_types_macro_4_byte.h b/pw_tokenizer/public/pw_tokenizer/internal/argument_types_macro_4_byte.h
new file mode 100644
index 0000000..dbb49fd
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/internal/argument_types_macro_4_byte.h
@@ -0,0 +1,59 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// AUTOGENERATED - DO NOT EDIT
+//
+// This file was generated by generate_argument_types_macro.py.
+// To make changes, update the script and run it to generate new files.
+#pragma once
+
+// Macro for encoding tokenizer argument types into an 4-byte value.
+//
+// PW_TOKENIZER_ARG_TYPES could be implemented with recursive macro expansion,
+// but that seems to compile a little slower. Instead, the full macro is
+// generated with Python code. This file is best viewed with line wrapping
+// disabled.
+//
+// These macros depend on macros in pw_tokenizer/internal/argument_types.h.
+// clang-format off
+
+#define _PW_TOKENIZER_TYPES_1(a1) (_PW_VARARGS_TYPE(a1) << 4 | 1)
+
+#define _PW_TOKENIZER_TYPES_2(a1, a2) (_PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 2)
+
+#define _PW_TOKENIZER_TYPES_3(a1, a2, a3) (_PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 3)
+
+#define _PW_TOKENIZER_TYPES_4(a1, a2, a3, a4) (_PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 4)
+
+#define _PW_TOKENIZER_TYPES_5(a1, a2, a3, a4, a5) (_PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 5)
+
+#define _PW_TOKENIZER_TYPES_6(a1, a2, a3, a4, a5, a6) (_PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 6)
+
+#define _PW_TOKENIZER_TYPES_7(a1, a2, a3, a4, a5, a6, a7) (_PW_VARARGS_TYPE(a7) << 16 | _PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 7)
+
+#define _PW_TOKENIZER_TYPES_8(a1, a2, a3, a4, a5, a6, a7, a8) (_PW_VARARGS_TYPE(a8) << 18 | _PW_VARARGS_TYPE(a7) << 16 | _PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 8)
+
+#define _PW_TOKENIZER_TYPES_9(a1, a2, a3, a4, a5, a6, a7, a8, a9) (_PW_VARARGS_TYPE(a9) << 20 | _PW_VARARGS_TYPE(a8) << 18 | _PW_VARARGS_TYPE(a7) << 16 | _PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 9)
+
+#define _PW_TOKENIZER_TYPES_10(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) (_PW_VARARGS_TYPE(a10) << 22 | _PW_VARARGS_TYPE(a9) << 20 | _PW_VARARGS_TYPE(a8) << 18 | _PW_VARARGS_TYPE(a7) << 16 | _PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 10)
+
+#define _PW_TOKENIZER_TYPES_11(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) (_PW_VARARGS_TYPE(a11) << 24 | _PW_VARARGS_TYPE(a10) << 22 | _PW_VARARGS_TYPE(a9) << 20 | _PW_VARARGS_TYPE(a8) << 18 | _PW_VARARGS_TYPE(a7) << 16 | _PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 11)
+
+#define _PW_TOKENIZER_TYPES_12(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) (_PW_VARARGS_TYPE(a12) << 26 | _PW_VARARGS_TYPE(a11) << 24 | _PW_VARARGS_TYPE(a10) << 22 | _PW_VARARGS_TYPE(a9) << 20 | _PW_VARARGS_TYPE(a8) << 18 | _PW_VARARGS_TYPE(a7) << 16 | _PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 12)
+
+#define _PW_TOKENIZER_TYPES_13(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) (_PW_VARARGS_TYPE(a13) << 28 | _PW_VARARGS_TYPE(a12) << 26 | _PW_VARARGS_TYPE(a11) << 24 | _PW_VARARGS_TYPE(a10) << 22 | _PW_VARARGS_TYPE(a9) << 20 | _PW_VARARGS_TYPE(a8) << 18 | _PW_VARARGS_TYPE(a7) << 16 | _PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 13)
+
+#define _PW_TOKENIZER_TYPES_14(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) (_PW_VARARGS_TYPE(a14) << 30 | _PW_VARARGS_TYPE(a13) << 28 | _PW_VARARGS_TYPE(a12) << 26 | _PW_VARARGS_TYPE(a11) << 24 | _PW_VARARGS_TYPE(a10) << 22 | _PW_VARARGS_TYPE(a9) << 20 | _PW_VARARGS_TYPE(a8) << 18 | _PW_VARARGS_TYPE(a7) << 16 | _PW_VARARGS_TYPE(a6) << 14 | _PW_VARARGS_TYPE(a5) << 12 | _PW_VARARGS_TYPE(a4) << 10 | _PW_VARARGS_TYPE(a3) << 8 | _PW_VARARGS_TYPE(a2) << 6 | _PW_VARARGS_TYPE(a1) << 4 | 14)
+
+// clang-format on
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/argument_types_macro_8_byte.h b/pw_tokenizer/public/pw_tokenizer/internal/argument_types_macro_8_byte.h
new file mode 100644
index 0000000..1a83256
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/internal/argument_types_macro_8_byte.h
@@ -0,0 +1,89 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// AUTOGENERATED - DO NOT EDIT
+//
+// This file was generated by generate_argument_types_macro.py.
+// To make changes, update the script and run it to generate new files.
+#pragma once
+
+// Macro for encoding tokenizer argument types into an 8-byte value.
+//
+// PW_TOKENIZER_ARG_TYPES could be implemented with recursive macro expansion,
+// but that seems to compile a little slower. Instead, the full macro is
+// generated with Python code. This file is best viewed with line wrapping
+// disabled.
+//
+// These macros depend on macros in pw_tokenizer/internal/argument_types.h.
+// clang-format off
+
+#define _PW_TOKENIZER_TYPES_1(a1) (_PW_VARARGS_TYPE(a1) << 6 | 1)
+
+#define _PW_TOKENIZER_TYPES_2(a1, a2) (_PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 2)
+
+#define _PW_TOKENIZER_TYPES_3(a1, a2, a3) (_PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 3)
+
+#define _PW_TOKENIZER_TYPES_4(a1, a2, a3, a4) (_PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 4)
+
+#define _PW_TOKENIZER_TYPES_5(a1, a2, a3, a4, a5) (_PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 5)
+
+#define _PW_TOKENIZER_TYPES_6(a1, a2, a3, a4, a5, a6) (_PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 6)
+
+#define _PW_TOKENIZER_TYPES_7(a1, a2, a3, a4, a5, a6, a7) (_PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 7)
+
+#define _PW_TOKENIZER_TYPES_8(a1, a2, a3, a4, a5, a6, a7, a8) (_PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 8)
+
+#define _PW_TOKENIZER_TYPES_9(a1, a2, a3, a4, a5, a6, a7, a8, a9) (_PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 9)
+
+#define _PW_TOKENIZER_TYPES_10(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) (_PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 10)
+
+#define _PW_TOKENIZER_TYPES_11(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) (_PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 11)
+
+#define _PW_TOKENIZER_TYPES_12(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) (_PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 12)
+
+#define _PW_TOKENIZER_TYPES_13(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) (_PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 13)
+
+#define _PW_TOKENIZER_TYPES_14(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) (_PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 14)
+
+#define _PW_TOKENIZER_TYPES_15(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) (_PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 15)
+
+#define _PW_TOKENIZER_TYPES_16(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) (_PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 16)
+
+#define _PW_TOKENIZER_TYPES_17(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) (_PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 17)
+
+#define _PW_TOKENIZER_TYPES_18(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) (_PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 18)
+
+#define _PW_TOKENIZER_TYPES_19(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) (_PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 19)
+
+#define _PW_TOKENIZER_TYPES_20(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) (_PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 20)
+
+#define _PW_TOKENIZER_TYPES_21(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) (_PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 21)
+
+#define _PW_TOKENIZER_TYPES_22(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) (_PW_VARARGS_TYPE(a22) << 48 | _PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 22)
+
+#define _PW_TOKENIZER_TYPES_23(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23) (_PW_VARARGS_TYPE(a23) << 50 | _PW_VARARGS_TYPE(a22) << 48 | _PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 23)
+
+#define _PW_TOKENIZER_TYPES_24(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24) (_PW_VARARGS_TYPE(a24) << 52 | _PW_VARARGS_TYPE(a23) << 50 | _PW_VARARGS_TYPE(a22) << 48 | _PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 24)
+
+#define _PW_TOKENIZER_TYPES_25(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) (_PW_VARARGS_TYPE(a25) << 54 | _PW_VARARGS_TYPE(a24) << 52 | _PW_VARARGS_TYPE(a23) << 50 | _PW_VARARGS_TYPE(a22) << 48 | _PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 25)
+
+#define _PW_TOKENIZER_TYPES_26(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26) (_PW_VARARGS_TYPE(a26) << 56 | _PW_VARARGS_TYPE(a25) << 54 | _PW_VARARGS_TYPE(a24) << 52 | _PW_VARARGS_TYPE(a23) << 50 | _PW_VARARGS_TYPE(a22) << 48 | _PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 26)
+
+#define _PW_TOKENIZER_TYPES_27(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27) (_PW_VARARGS_TYPE(a27) << 58 | _PW_VARARGS_TYPE(a26) << 56 | _PW_VARARGS_TYPE(a25) << 54 | _PW_VARARGS_TYPE(a24) << 52 | _PW_VARARGS_TYPE(a23) << 50 | _PW_VARARGS_TYPE(a22) << 48 | _PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 27)
+
+#define _PW_TOKENIZER_TYPES_28(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28) (_PW_VARARGS_TYPE(a28) << 60 | _PW_VARARGS_TYPE(a27) << 58 | _PW_VARARGS_TYPE(a26) << 56 | _PW_VARARGS_TYPE(a25) << 54 | _PW_VARARGS_TYPE(a24) << 52 | _PW_VARARGS_TYPE(a23) << 50 | _PW_VARARGS_TYPE(a22) << 48 | _PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 28)
+
+#define _PW_TOKENIZER_TYPES_29(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29) (_PW_VARARGS_TYPE(a29) << 62 | _PW_VARARGS_TYPE(a28) << 60 | _PW_VARARGS_TYPE(a27) << 58 | _PW_VARARGS_TYPE(a26) << 56 | _PW_VARARGS_TYPE(a25) << 54 | _PW_VARARGS_TYPE(a24) << 52 | _PW_VARARGS_TYPE(a23) << 50 | _PW_VARARGS_TYPE(a22) << 48 | _PW_VARARGS_TYPE(a21) << 46 | _PW_VARARGS_TYPE(a20) << 44 | _PW_VARARGS_TYPE(a19) << 42 | _PW_VARARGS_TYPE(a18) << 40 | _PW_VARARGS_TYPE(a17) << 38 | _PW_VARARGS_TYPE(a16) << 36 | _PW_VARARGS_TYPE(a15) << 34 | _PW_VARARGS_TYPE(a14) << 32 | _PW_VARARGS_TYPE(a13) << 30 | _PW_VARARGS_TYPE(a12) << 28 | _PW_VARARGS_TYPE(a11) << 26 | _PW_VARARGS_TYPE(a10) << 24 | _PW_VARARGS_TYPE(a9) << 22 | _PW_VARARGS_TYPE(a8) << 20 | _PW_VARARGS_TYPE(a7) << 18 | _PW_VARARGS_TYPE(a6) << 16 | _PW_VARARGS_TYPE(a5) << 14 | _PW_VARARGS_TYPE(a4) << 12 | _PW_VARARGS_TYPE(a3) << 10 | _PW_VARARGS_TYPE(a2) << 8 | _PW_VARARGS_TYPE(a1) << 6 | 29)
+
+// clang-format on
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/decode.h b/pw_tokenizer/public/pw_tokenizer/internal/decode.h
new file mode 100644
index 0000000..8cdf690
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/internal/decode.h
@@ -0,0 +1,267 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// decode.h defines classes that implement tokenized string decoding. These
+// classes should not be used directly; instead decode tokenized messages with
+// the Detokenizer class, defined in pw_tokenizer/detokenize.h.
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "pw_span/span.h"
+
+// Decoding errors are marked with prefix and suffix so that they stand out from
+// the rest of the decoded strings. These macros are used to build decoding
+// error strings.
+#define PW_TOKENIZER_ARG_DECODING_ERROR_PREFIX "<["
+#define PW_TOKENIZER_ARG_DECODING_ERROR_SUFFIX "]>"
+#define PW_TOKENIZER_ARG_DECODING_ERROR(message) \
+  PW_TOKENIZER_ARG_DECODING_ERROR_PREFIX message \
+      PW_TOKENIZER_ARG_DECODING_ERROR_SUFFIX
+
+namespace pw::tokenizer {
+
+// The status of an argument that was decoded from an encoded tokenized string.
+// This enum should match the values in decode.py's DecodedArg class.
+class ArgStatus {
+ public:
+  // The Code enum tracks issues arise when decoding a tokenized string
+  // argument. Each value is one bit, and an ArgStatus will have multiple bits
+  // set if multiple issues are encountered.
+  enum Code : unsigned {
+    kOk = 0,           // Decoding was successful.
+    kMissing = 1,      // The argument was not present in the data.
+    kTruncated = 2,    // The argument was truncated during encoding.
+    kDecodeError = 4,  // An error occurred while decoding the argument.
+    kSkipped = 8,      // Argument was skipped due to a previous error.
+  };
+
+  constexpr ArgStatus(Code code = kOk) : status_(code) {}
+
+  // Sets additional status bits.
+  constexpr void Update(ArgStatus status) { status_ |= status.status_; }
+
+  // True if no decoding errors occurred. Truncated is considered OK, since
+  // encoding and decoding still occurs successfully when a string is truncated.
+  constexpr bool ok() const { return status_ == kOk || status_ == kTruncated; }
+
+  // Checks if an error flag is set in the status.
+  constexpr bool HasError(Code code) const { return (status_ & code) != 0u; }
+
+ private:
+  // Since multiple Code bits may be set in an ArgStatus, the status is stored
+  // as an unsigned instead of a Code.
+  unsigned status_;
+};
+
+// An argument decoded from an encoded tokenized message.
+class DecodedArg {
+ public:
+  // Constructs a DecodedArg from a decoded value. The value is formatted into a
+  // string using the provided format string. The number of bytes that were
+  // decoded to get the value are provided in raw_size_bytes.
+  template <typename ArgumentType>
+  static DecodedArg FromValue(const char* format_string,
+                              ArgumentType value,
+                              size_t raw_size_bytes,
+                              ArgStatus arg_status = ArgStatus::kOk);
+
+  // Constructs a DecodedArg that represents a string literal in the format
+  // string (plain text or % character).
+  DecodedArg(const std::string& literal)
+      : value_(literal), raw_data_size_bytes_(0) {}
+
+  // Constructs a DecodedArg that encountered an error during decoding.
+  DecodedArg(ArgStatus error,
+             const std::string_view& spec,
+             size_t raw_size_bytes = 0u,
+             const std::string_view& value = {});
+
+  // This argument's value as a string. If an error occurred while decoding this
+  // argument, value() will be an error message.
+  const std::string& value() const { return value_; }
+
+  // Returns the conversion specification for this argument (e.g. %02x). This is
+  // empty for literals or "%%".
+  const std::string& spec() const { return spec_; }
+
+  // True if this argument decoded successfully.
+  bool ok() const { return status_.ok(); }
+
+  // How many bytes this arg occupied in the encoded arguments.
+  size_t raw_size_bytes() const { return raw_data_size_bytes_; }
+
+ private:
+  DecodedArg(const char* format, size_t raw_size_bytes, ArgStatus status)
+      : spec_(format), raw_data_size_bytes_(raw_size_bytes), status_(status) {}
+
+  std::string value_;
+  std::string spec_;
+  size_t raw_data_size_bytes_;
+  ArgStatus status_;
+};
+
+// Represents a segment of a printf-style format string. Each StringSegment
+// contains either literal text or a format specifier.
+class StringSegment {
+ public:
+  // Parses a format specifier from the text and returns a StringSegment that
+  // represents it. Returns an empty StringSegment if no valid format specifier
+  // was found.
+  static StringSegment ParseFormatSpec(const char* format);
+
+  // Creates a StringSegment that represents a piece of plain text.
+  StringSegment(const std::string_view& text) : StringSegment(text, kLiteral) {}
+
+  // Returns the DecodedArg with this StringSegment decoded according to the
+  // provided arguments.
+  DecodedArg Decode(const span<const uint8_t>& arguments) const;
+
+  // Skips decoding this StringSegment. Literals and %% are expanded as normal.
+  DecodedArg Skip() const;
+
+  bool empty() const { return text_.empty(); }
+
+  const std::string& text() const { return text_; }
+
+ private:
+  enum Type {
+    kLiteral,
+    kPercent,  // %% format specifier
+    kString,
+    kSignedInt,
+    kUnsigned32,
+    kUnsigned64,
+    kFloatingPoint,
+  };
+
+  // Varargs-promoted size of args on this machine; only needed for ints or %p.
+  enum ArgSize : bool { k32Bit, k64Bit };
+
+  template <typename T>
+  static constexpr ArgSize VarargSize() {
+    return sizeof(T) == sizeof(int64_t) ? k64Bit : k32Bit;
+  }
+
+  static ArgSize VarargSize(std::array<char, 2> length, char spec);
+
+  StringSegment() : type_(kLiteral) {}
+
+  StringSegment(const std::string_view& text, Type type)
+      : StringSegment(text, type, VarargSize<void*>()) {}
+
+  StringSegment(const std::string_view& text, Type type, ArgSize local_size)
+      : text_(text), type_(type), local_size_(local_size) {}
+
+  DecodedArg DecodeString(const span<const uint8_t>& arguments) const;
+
+  DecodedArg DecodeInteger(const span<const uint8_t>& arguments) const;
+
+  DecodedArg DecodeFloatingPoint(const span<const uint8_t>& arguments) const;
+
+  std::string text_;
+  Type type_;
+  ArgSize local_size_;  // Arg size to use for snprintf on this machine.
+};
+
+// The result of decoding a tokenized message with a FormatString. Stores
+// decoded arguments and whether there was any undecoded data. This is returned
+// from a FormatString::Format call.
+class DecodedFormatString {
+ public:
+  DecodedFormatString(std::vector<DecodedArg>&& segments,
+                      size_t remaining_bytes)
+      : segments_(std::move(segments)), remaining_bytes_(remaining_bytes) {}
+
+  DecodedFormatString(const DecodedFormatString&) = default;
+  DecodedFormatString(DecodedFormatString&&) = default;
+
+  DecodedFormatString& operator=(const DecodedFormatString&) = default;
+  DecodedFormatString& operator=(DecodedFormatString&&) = default;
+
+  // Returns the decoded format string. If any argument decoding errors
+  // occurred, the % conversion specifiers are included unmodified.
+  std::string value() const;
+
+  // Returns the decoded format string, with error messages for any arguments
+  // that failed to decode.
+  std::string value_with_errors() const;
+
+  bool ok() const { return remaining_bytes() == 0u && decoding_errors() == 0u; }
+
+  // Returns the number of bytes that remained after decoding.
+  size_t remaining_bytes() const { return remaining_bytes_; }
+
+  // Returns the number of arguments in the format string. %% is not included.
+  size_t argument_count() const;
+
+  // Returns the number of arguments that failed to decode.
+  size_t decoding_errors() const;
+
+ private:
+  std::vector<DecodedArg> segments_;
+  size_t remaining_bytes_;
+};
+
+// Represents a printf-style format string. The string is stored as a vector of
+// StringSegments.
+class FormatString {
+ public:
+  // Constructs a FormatString from a null-terminated format string.
+  FormatString(const char* format_string);
+
+  // Formats this format string according to the provided encoded arguments and
+  // returns a string.
+  DecodedFormatString Format(span<const uint8_t> arguments) const;
+
+  DecodedFormatString Format(const std::string_view& arguments) const {
+    return Format(span(reinterpret_cast<const uint8_t*>(arguments.data()),
+                       arguments.size()));
+  }
+
+ private:
+  std::vector<StringSegment> segments_;
+};
+
+// Implementation of DecodedArg::FromValue template function.
+template <typename ArgumentType>
+DecodedArg DecodedArg::FromValue(const char* format,
+                                 ArgumentType value,
+                                 size_t raw_size_bytes,
+                                 ArgStatus status) {
+  DecodedArg arg(format, raw_size_bytes, status);
+  const int value_size = std::snprintf(nullptr, 0u, format, value);
+
+  if (value_size < 0) {
+    arg.status_.Update(ArgStatus::kDecodeError);
+    return arg;
+  }
+
+  // Reserve space in the value string for the snprintf call.
+  arg.value_.append(value_size + 1, '\0');
+
+  // Print the value to the string in the reserved space, then pop off the \0.
+  std::snprintf(arg.value_.data(), arg.value_.size(), format, value);
+  arg.value_.pop_back();  // Remove the trailing \0.
+
+  return arg;
+}
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h b/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h
new file mode 100644
index 0000000..1c4eb82
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h
@@ -0,0 +1,161 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// AUTOGENERATED - DO NOT EDIT
+//
+// This file was generated by generate_hash_macro.py.
+// To make changes, update the script and run it to regenerate the files.
+#pragma once
+
+#include <stdint.h>
+
+// 128-character version of the tokenizer hash function.
+//
+// The argument must be a string literal. It is concatenated with "" to ensure
+// that this is the case.
+//
+// clang-format off
+
+#define PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH(str)                          \
+  (uint32_t)(sizeof(str "") - 1 + /* The argument must be a string literal. */ \
+             0x0001003fu * (uint8_t)str[0] +                                   \
+             0x007e0f81u * (uint8_t)(  1 < sizeof(str) ? str[  1] : 0) +       \
+             0x2e86d0bfu * (uint8_t)(  2 < sizeof(str) ? str[  2] : 0) +       \
+             0x43ec5f01u * (uint8_t)(  3 < sizeof(str) ? str[  3] : 0) +       \
+             0x162c613fu * (uint8_t)(  4 < sizeof(str) ? str[  4] : 0) +       \
+             0xd62aee81u * (uint8_t)(  5 < sizeof(str) ? str[  5] : 0) +       \
+             0xa311b1bfu * (uint8_t)(  6 < sizeof(str) ? str[  6] : 0) +       \
+             0xd319be01u * (uint8_t)(  7 < sizeof(str) ? str[  7] : 0) +       \
+             0xb156c23fu * (uint8_t)(  8 < sizeof(str) ? str[  8] : 0) +       \
+             0x6698cd81u * (uint8_t)(  9 < sizeof(str) ? str[  9] : 0) +       \
+             0x0d1b92bfu * (uint8_t)( 10 < sizeof(str) ? str[ 10] : 0) +       \
+             0xcc881d01u * (uint8_t)( 11 < sizeof(str) ? str[ 11] : 0) +       \
+             0x7280233fu * (uint8_t)( 12 < sizeof(str) ? str[ 12] : 0) +       \
+             0x50c7ac81u * (uint8_t)( 13 < sizeof(str) ? str[ 13] : 0) +       \
+             0x8da473bfu * (uint8_t)( 14 < sizeof(str) ? str[ 14] : 0) +       \
+             0x4f377c01u * (uint8_t)( 15 < sizeof(str) ? str[ 15] : 0) +       \
+             0xfaa8843fu * (uint8_t)( 16 < sizeof(str) ? str[ 16] : 0) +       \
+             0x33b78b81u * (uint8_t)( 17 < sizeof(str) ? str[ 17] : 0) +       \
+             0x45ac54bfu * (uint8_t)( 18 < sizeof(str) ? str[ 18] : 0) +       \
+             0x7a27db01u * (uint8_t)( 19 < sizeof(str) ? str[ 19] : 0) +       \
+             0xeacfe53fu * (uint8_t)( 20 < sizeof(str) ? str[ 20] : 0) +       \
+             0xae686a81u * (uint8_t)( 21 < sizeof(str) ? str[ 21] : 0) +       \
+             0x563335bfu * (uint8_t)( 22 < sizeof(str) ? str[ 22] : 0) +       \
+             0x6c593a01u * (uint8_t)( 23 < sizeof(str) ? str[ 23] : 0) +       \
+             0xe3f6463fu * (uint8_t)( 24 < sizeof(str) ? str[ 24] : 0) +       \
+             0x5fda4981u * (uint8_t)( 25 < sizeof(str) ? str[ 25] : 0) +       \
+             0xe03916bfu * (uint8_t)( 26 < sizeof(str) ? str[ 26] : 0) +       \
+             0x44cb9901u * (uint8_t)( 27 < sizeof(str) ? str[ 27] : 0) +       \
+             0x871ba73fu * (uint8_t)( 28 < sizeof(str) ? str[ 28] : 0) +       \
+             0xe70d2881u * (uint8_t)( 29 < sizeof(str) ? str[ 29] : 0) +       \
+             0x04bdf7bfu * (uint8_t)( 30 < sizeof(str) ? str[ 30] : 0) +       \
+             0x227ef801u * (uint8_t)( 31 < sizeof(str) ? str[ 31] : 0) +       \
+             0x7540083fu * (uint8_t)( 32 < sizeof(str) ? str[ 32] : 0) +       \
+             0xe3010781u * (uint8_t)( 33 < sizeof(str) ? str[ 33] : 0) +       \
+             0xe4c1d8bfu * (uint8_t)( 34 < sizeof(str) ? str[ 34] : 0) +       \
+             0x24735701u * (uint8_t)( 35 < sizeof(str) ? str[ 35] : 0) +       \
+             0x4f63693fu * (uint8_t)( 36 < sizeof(str) ? str[ 36] : 0) +       \
+             0xf2b5e681u * (uint8_t)( 37 < sizeof(str) ? str[ 37] : 0) +       \
+             0xa144b9bfu * (uint8_t)( 38 < sizeof(str) ? str[ 38] : 0) +       \
+             0x69a8b601u * (uint8_t)( 39 < sizeof(str) ? str[ 39] : 0) +       \
+             0xb685ca3fu * (uint8_t)( 40 < sizeof(str) ? str[ 40] : 0) +       \
+             0xb52bc581u * (uint8_t)( 41 < sizeof(str) ? str[ 41] : 0) +       \
+             0x5b469abfu * (uint8_t)( 42 < sizeof(str) ? str[ 42] : 0) +       \
+             0x111f1501u * (uint8_t)( 43 < sizeof(str) ? str[ 43] : 0) +       \
+             0x4ba72b3fu * (uint8_t)( 44 < sizeof(str) ? str[ 44] : 0) +       \
+             0xc962a481u * (uint8_t)( 45 < sizeof(str) ? str[ 45] : 0) +       \
+             0x33c77bbfu * (uint8_t)( 46 < sizeof(str) ? str[ 46] : 0) +       \
+             0x39d67401u * (uint8_t)( 47 < sizeof(str) ? str[ 47] : 0) +       \
+             0xafc78c3fu * (uint8_t)( 48 < sizeof(str) ? str[ 48] : 0) +       \
+             0xce5a8381u * (uint8_t)( 49 < sizeof(str) ? str[ 49] : 0) +       \
+             0x4bc75cbfu * (uint8_t)( 50 < sizeof(str) ? str[ 50] : 0) +       \
+             0x02ced301u * (uint8_t)( 51 < sizeof(str) ? str[ 51] : 0) +       \
+             0x83e6ed3fu * (uint8_t)( 52 < sizeof(str) ? str[ 52] : 0) +       \
+             0x63136281u * (uint8_t)( 53 < sizeof(str) ? str[ 53] : 0) +       \
+             0xc4463dbfu * (uint8_t)( 54 < sizeof(str) ? str[ 54] : 0) +       \
+             0x8b083201u * (uint8_t)( 55 < sizeof(str) ? str[ 55] : 0) +       \
+             0x69054e3fu * (uint8_t)( 56 < sizeof(str) ? str[ 56] : 0) +       \
+             0x268d4181u * (uint8_t)( 57 < sizeof(str) ? str[ 57] : 0) +       \
+             0xbe441ebfu * (uint8_t)( 58 < sizeof(str) ? str[ 58] : 0) +       \
+             0xf1829101u * (uint8_t)( 59 < sizeof(str) ? str[ 59] : 0) +       \
+             0x0022af3fu * (uint8_t)( 60 < sizeof(str) ? str[ 60] : 0) +       \
+             0xb7c82081u * (uint8_t)( 61 < sizeof(str) ? str[ 61] : 0) +       \
+             0x5ac0ffbfu * (uint8_t)( 62 < sizeof(str) ? str[ 62] : 0) +       \
+             0x553df001u * (uint8_t)( 63 < sizeof(str) ? str[ 63] : 0) +       \
+             0xea3f103fu * (uint8_t)( 64 < sizeof(str) ? str[ 64] : 0) +       \
+             0xb5c3ff81u * (uint8_t)( 65 < sizeof(str) ? str[ 65] : 0) +       \
+             0xbabce0bfu * (uint8_t)( 66 < sizeof(str) ? str[ 66] : 0) +       \
+             0xd53a4f01u * (uint8_t)( 67 < sizeof(str) ? str[ 67] : 0) +       \
+             0xc85a713fu * (uint8_t)( 68 < sizeof(str) ? str[ 68] : 0) +       \
+             0xbf80de81u * (uint8_t)( 69 < sizeof(str) ? str[ 69] : 0) +       \
+             0xff37c1bfu * (uint8_t)( 70 < sizeof(str) ? str[ 70] : 0) +       \
+             0x9077ae01u * (uint8_t)( 71 < sizeof(str) ? str[ 71] : 0) +       \
+             0x3b74d23fu * (uint8_t)( 72 < sizeof(str) ? str[ 72] : 0) +       \
+             0x73febd81u * (uint8_t)( 73 < sizeof(str) ? str[ 73] : 0) +       \
+             0x4931a2bfu * (uint8_t)( 74 < sizeof(str) ? str[ 74] : 0) +       \
+             0xa5f60d01u * (uint8_t)( 75 < sizeof(str) ? str[ 75] : 0) +       \
+             0xe48e333fu * (uint8_t)( 76 < sizeof(str) ? str[ 76] : 0) +       \
+             0x723d9c81u * (uint8_t)( 77 < sizeof(str) ? str[ 77] : 0) +       \
+             0xb9aa83bfu * (uint8_t)( 78 < sizeof(str) ? str[ 78] : 0) +       \
+             0x34b56c01u * (uint8_t)( 79 < sizeof(str) ? str[ 79] : 0) +       \
+             0x64a6943fu * (uint8_t)( 80 < sizeof(str) ? str[ 80] : 0) +       \
+             0x593d7b81u * (uint8_t)( 81 < sizeof(str) ? str[ 81] : 0) +       \
+             0x71a264bfu * (uint8_t)( 82 < sizeof(str) ? str[ 82] : 0) +       \
+             0x5bb5cb01u * (uint8_t)( 83 < sizeof(str) ? str[ 83] : 0) +       \
+             0x5cbdf53fu * (uint8_t)( 84 < sizeof(str) ? str[ 84] : 0) +       \
+             0xc7fe5a81u * (uint8_t)( 85 < sizeof(str) ? str[ 85] : 0) +       \
+             0x921945bfu * (uint8_t)( 86 < sizeof(str) ? str[ 86] : 0) +       \
+             0x39f72a01u * (uint8_t)( 87 < sizeof(str) ? str[ 87] : 0) +       \
+             0x6dd4563fu * (uint8_t)( 88 < sizeof(str) ? str[ 88] : 0) +       \
+             0x5d803981u * (uint8_t)( 89 < sizeof(str) ? str[ 89] : 0) +       \
+             0x3c0f26bfu * (uint8_t)( 90 < sizeof(str) ? str[ 90] : 0) +       \
+             0xee798901u * (uint8_t)( 91 < sizeof(str) ? str[ 91] : 0) +       \
+             0x38e9b73fu * (uint8_t)( 92 < sizeof(str) ? str[ 92] : 0) +       \
+             0xb8c31881u * (uint8_t)( 93 < sizeof(str) ? str[ 93] : 0) +       \
+             0x908407bfu * (uint8_t)( 94 < sizeof(str) ? str[ 94] : 0) +       \
+             0x983ce801u * (uint8_t)( 95 < sizeof(str) ? str[ 95] : 0) +       \
+             0x5efe183fu * (uint8_t)( 96 < sizeof(str) ? str[ 96] : 0) +       \
+             0x78c6f781u * (uint8_t)( 97 < sizeof(str) ? str[ 97] : 0) +       \
+             0xb077e8bfu * (uint8_t)( 98 < sizeof(str) ? str[ 98] : 0) +       \
+             0x56414701u * (uint8_t)( 99 < sizeof(str) ? str[ 99] : 0) +       \
+             0x8111793fu * (uint8_t)(100 < sizeof(str) ? str[100] : 0) +       \
+             0x3c8bd681u * (uint8_t)(101 < sizeof(str) ? str[101] : 0) +       \
+             0xbceac9bfu * (uint8_t)(102 < sizeof(str) ? str[102] : 0) +       \
+             0x4786a601u * (uint8_t)(103 < sizeof(str) ? str[103] : 0) +       \
+             0x4023da3fu * (uint8_t)(104 < sizeof(str) ? str[104] : 0) +       \
+             0xa311b581u * (uint8_t)(105 < sizeof(str) ? str[105] : 0) +       \
+             0xd6dcaabfu * (uint8_t)(106 < sizeof(str) ? str[106] : 0) +       \
+             0x8b0d0501u * (uint8_t)(107 < sizeof(str) ? str[107] : 0) +       \
+             0x3d353b3fu * (uint8_t)(108 < sizeof(str) ? str[108] : 0) +       \
+             0x4b589481u * (uint8_t)(109 < sizeof(str) ? str[109] : 0) +       \
+             0x1f4d8bbfu * (uint8_t)(110 < sizeof(str) ? str[110] : 0) +       \
+             0x3fd46401u * (uint8_t)(111 < sizeof(str) ? str[111] : 0) +       \
+             0x19459c3fu * (uint8_t)(112 < sizeof(str) ? str[112] : 0) +       \
+             0xd4607381u * (uint8_t)(113 < sizeof(str) ? str[113] : 0) +       \
+             0xb73d6cbfu * (uint8_t)(114 < sizeof(str) ? str[114] : 0) +       \
+             0x84dcc301u * (uint8_t)(115 < sizeof(str) ? str[115] : 0) +       \
+             0x7554fd3fu * (uint8_t)(116 < sizeof(str) ? str[116] : 0) +       \
+             0xdd295281u * (uint8_t)(117 < sizeof(str) ? str[117] : 0) +       \
+             0xbfac4dbfu * (uint8_t)(118 < sizeof(str) ? str[118] : 0) +       \
+             0x79262201u * (uint8_t)(119 < sizeof(str) ? str[119] : 0) +       \
+             0xf2635e3fu * (uint8_t)(120 < sizeof(str) ? str[120] : 0) +       \
+             0x04b33181u * (uint8_t)(121 < sizeof(str) ? str[121] : 0) +       \
+             0x599a2ebfu * (uint8_t)(122 < sizeof(str) ? str[122] : 0) +       \
+             0x3bb08101u * (uint8_t)(123 < sizeof(str) ? str[123] : 0) +       \
+             0x3170bf3fu * (uint8_t)(124 < sizeof(str) ? str[124] : 0) +       \
+             0xe9fe1081u * (uint8_t)(125 < sizeof(str) ? str[125] : 0) +       \
+             0xa6070fbfu * (uint8_t)(126 < sizeof(str) ? str[126] : 0) +       \
+             0xeb7be001u * (uint8_t)(127 < sizeof(str) ? str[127] : 0))
+
+// clang-format on
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h b/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h
new file mode 100644
index 0000000..857c61f
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h
@@ -0,0 +1,113 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// AUTOGENERATED - DO NOT EDIT
+//
+// This file was generated by generate_hash_macro.py.
+// To make changes, update the script and run it to regenerate the files.
+#pragma once
+
+#include <stdint.h>
+
+// 80-character version of the tokenizer hash function.
+//
+// The argument must be a string literal. It is concatenated with "" to ensure
+// that this is the case.
+//
+// clang-format off
+
+#define PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH(str)                           \
+  (uint32_t)(sizeof(str "") - 1 + /* The argument must be a string literal. */ \
+             0x0001003fu * (uint8_t)str[0] +                                   \
+             0x007e0f81u * (uint8_t)( 1 < sizeof(str) ? str[ 1] : 0) +         \
+             0x2e86d0bfu * (uint8_t)( 2 < sizeof(str) ? str[ 2] : 0) +         \
+             0x43ec5f01u * (uint8_t)( 3 < sizeof(str) ? str[ 3] : 0) +         \
+             0x162c613fu * (uint8_t)( 4 < sizeof(str) ? str[ 4] : 0) +         \
+             0xd62aee81u * (uint8_t)( 5 < sizeof(str) ? str[ 5] : 0) +         \
+             0xa311b1bfu * (uint8_t)( 6 < sizeof(str) ? str[ 6] : 0) +         \
+             0xd319be01u * (uint8_t)( 7 < sizeof(str) ? str[ 7] : 0) +         \
+             0xb156c23fu * (uint8_t)( 8 < sizeof(str) ? str[ 8] : 0) +         \
+             0x6698cd81u * (uint8_t)( 9 < sizeof(str) ? str[ 9] : 0) +         \
+             0x0d1b92bfu * (uint8_t)(10 < sizeof(str) ? str[10] : 0) +         \
+             0xcc881d01u * (uint8_t)(11 < sizeof(str) ? str[11] : 0) +         \
+             0x7280233fu * (uint8_t)(12 < sizeof(str) ? str[12] : 0) +         \
+             0x50c7ac81u * (uint8_t)(13 < sizeof(str) ? str[13] : 0) +         \
+             0x8da473bfu * (uint8_t)(14 < sizeof(str) ? str[14] : 0) +         \
+             0x4f377c01u * (uint8_t)(15 < sizeof(str) ? str[15] : 0) +         \
+             0xfaa8843fu * (uint8_t)(16 < sizeof(str) ? str[16] : 0) +         \
+             0x33b78b81u * (uint8_t)(17 < sizeof(str) ? str[17] : 0) +         \
+             0x45ac54bfu * (uint8_t)(18 < sizeof(str) ? str[18] : 0) +         \
+             0x7a27db01u * (uint8_t)(19 < sizeof(str) ? str[19] : 0) +         \
+             0xeacfe53fu * (uint8_t)(20 < sizeof(str) ? str[20] : 0) +         \
+             0xae686a81u * (uint8_t)(21 < sizeof(str) ? str[21] : 0) +         \
+             0x563335bfu * (uint8_t)(22 < sizeof(str) ? str[22] : 0) +         \
+             0x6c593a01u * (uint8_t)(23 < sizeof(str) ? str[23] : 0) +         \
+             0xe3f6463fu * (uint8_t)(24 < sizeof(str) ? str[24] : 0) +         \
+             0x5fda4981u * (uint8_t)(25 < sizeof(str) ? str[25] : 0) +         \
+             0xe03916bfu * (uint8_t)(26 < sizeof(str) ? str[26] : 0) +         \
+             0x44cb9901u * (uint8_t)(27 < sizeof(str) ? str[27] : 0) +         \
+             0x871ba73fu * (uint8_t)(28 < sizeof(str) ? str[28] : 0) +         \
+             0xe70d2881u * (uint8_t)(29 < sizeof(str) ? str[29] : 0) +         \
+             0x04bdf7bfu * (uint8_t)(30 < sizeof(str) ? str[30] : 0) +         \
+             0x227ef801u * (uint8_t)(31 < sizeof(str) ? str[31] : 0) +         \
+             0x7540083fu * (uint8_t)(32 < sizeof(str) ? str[32] : 0) +         \
+             0xe3010781u * (uint8_t)(33 < sizeof(str) ? str[33] : 0) +         \
+             0xe4c1d8bfu * (uint8_t)(34 < sizeof(str) ? str[34] : 0) +         \
+             0x24735701u * (uint8_t)(35 < sizeof(str) ? str[35] : 0) +         \
+             0x4f63693fu * (uint8_t)(36 < sizeof(str) ? str[36] : 0) +         \
+             0xf2b5e681u * (uint8_t)(37 < sizeof(str) ? str[37] : 0) +         \
+             0xa144b9bfu * (uint8_t)(38 < sizeof(str) ? str[38] : 0) +         \
+             0x69a8b601u * (uint8_t)(39 < sizeof(str) ? str[39] : 0) +         \
+             0xb685ca3fu * (uint8_t)(40 < sizeof(str) ? str[40] : 0) +         \
+             0xb52bc581u * (uint8_t)(41 < sizeof(str) ? str[41] : 0) +         \
+             0x5b469abfu * (uint8_t)(42 < sizeof(str) ? str[42] : 0) +         \
+             0x111f1501u * (uint8_t)(43 < sizeof(str) ? str[43] : 0) +         \
+             0x4ba72b3fu * (uint8_t)(44 < sizeof(str) ? str[44] : 0) +         \
+             0xc962a481u * (uint8_t)(45 < sizeof(str) ? str[45] : 0) +         \
+             0x33c77bbfu * (uint8_t)(46 < sizeof(str) ? str[46] : 0) +         \
+             0x39d67401u * (uint8_t)(47 < sizeof(str) ? str[47] : 0) +         \
+             0xafc78c3fu * (uint8_t)(48 < sizeof(str) ? str[48] : 0) +         \
+             0xce5a8381u * (uint8_t)(49 < sizeof(str) ? str[49] : 0) +         \
+             0x4bc75cbfu * (uint8_t)(50 < sizeof(str) ? str[50] : 0) +         \
+             0x02ced301u * (uint8_t)(51 < sizeof(str) ? str[51] : 0) +         \
+             0x83e6ed3fu * (uint8_t)(52 < sizeof(str) ? str[52] : 0) +         \
+             0x63136281u * (uint8_t)(53 < sizeof(str) ? str[53] : 0) +         \
+             0xc4463dbfu * (uint8_t)(54 < sizeof(str) ? str[54] : 0) +         \
+             0x8b083201u * (uint8_t)(55 < sizeof(str) ? str[55] : 0) +         \
+             0x69054e3fu * (uint8_t)(56 < sizeof(str) ? str[56] : 0) +         \
+             0x268d4181u * (uint8_t)(57 < sizeof(str) ? str[57] : 0) +         \
+             0xbe441ebfu * (uint8_t)(58 < sizeof(str) ? str[58] : 0) +         \
+             0xf1829101u * (uint8_t)(59 < sizeof(str) ? str[59] : 0) +         \
+             0x0022af3fu * (uint8_t)(60 < sizeof(str) ? str[60] : 0) +         \
+             0xb7c82081u * (uint8_t)(61 < sizeof(str) ? str[61] : 0) +         \
+             0x5ac0ffbfu * (uint8_t)(62 < sizeof(str) ? str[62] : 0) +         \
+             0x553df001u * (uint8_t)(63 < sizeof(str) ? str[63] : 0) +         \
+             0xea3f103fu * (uint8_t)(64 < sizeof(str) ? str[64] : 0) +         \
+             0xb5c3ff81u * (uint8_t)(65 < sizeof(str) ? str[65] : 0) +         \
+             0xbabce0bfu * (uint8_t)(66 < sizeof(str) ? str[66] : 0) +         \
+             0xd53a4f01u * (uint8_t)(67 < sizeof(str) ? str[67] : 0) +         \
+             0xc85a713fu * (uint8_t)(68 < sizeof(str) ? str[68] : 0) +         \
+             0xbf80de81u * (uint8_t)(69 < sizeof(str) ? str[69] : 0) +         \
+             0xff37c1bfu * (uint8_t)(70 < sizeof(str) ? str[70] : 0) +         \
+             0x9077ae01u * (uint8_t)(71 < sizeof(str) ? str[71] : 0) +         \
+             0x3b74d23fu * (uint8_t)(72 < sizeof(str) ? str[72] : 0) +         \
+             0x73febd81u * (uint8_t)(73 < sizeof(str) ? str[73] : 0) +         \
+             0x4931a2bfu * (uint8_t)(74 < sizeof(str) ? str[74] : 0) +         \
+             0xa5f60d01u * (uint8_t)(75 < sizeof(str) ? str[75] : 0) +         \
+             0xe48e333fu * (uint8_t)(76 < sizeof(str) ? str[76] : 0) +         \
+             0x723d9c81u * (uint8_t)(77 < sizeof(str) ? str[77] : 0) +         \
+             0xb9aa83bfu * (uint8_t)(78 < sizeof(str) ? str[78] : 0) +         \
+             0x34b56c01u * (uint8_t)(79 < sizeof(str) ? str[79] : 0))
+
+// clang-format on
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h b/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h
new file mode 100644
index 0000000..799044c
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h
@@ -0,0 +1,129 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// AUTOGENERATED - DO NOT EDIT
+//
+// This file was generated by generate_hash_macro.py.
+// To make changes, update the script and run it to regenerate the files.
+#pragma once
+
+#include <stdint.h>
+
+// 96-character version of the tokenizer hash function.
+//
+// The argument must be a string literal. It is concatenated with "" to ensure
+// that this is the case.
+//
+// clang-format off
+
+#define PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH(str)                           \
+  (uint32_t)(sizeof(str "") - 1 + /* The argument must be a string literal. */ \
+             0x0001003fu * (uint8_t)str[0] +                                   \
+             0x007e0f81u * (uint8_t)( 1 < sizeof(str) ? str[ 1] : 0) +         \
+             0x2e86d0bfu * (uint8_t)( 2 < sizeof(str) ? str[ 2] : 0) +         \
+             0x43ec5f01u * (uint8_t)( 3 < sizeof(str) ? str[ 3] : 0) +         \
+             0x162c613fu * (uint8_t)( 4 < sizeof(str) ? str[ 4] : 0) +         \
+             0xd62aee81u * (uint8_t)( 5 < sizeof(str) ? str[ 5] : 0) +         \
+             0xa311b1bfu * (uint8_t)( 6 < sizeof(str) ? str[ 6] : 0) +         \
+             0xd319be01u * (uint8_t)( 7 < sizeof(str) ? str[ 7] : 0) +         \
+             0xb156c23fu * (uint8_t)( 8 < sizeof(str) ? str[ 8] : 0) +         \
+             0x6698cd81u * (uint8_t)( 9 < sizeof(str) ? str[ 9] : 0) +         \
+             0x0d1b92bfu * (uint8_t)(10 < sizeof(str) ? str[10] : 0) +         \
+             0xcc881d01u * (uint8_t)(11 < sizeof(str) ? str[11] : 0) +         \
+             0x7280233fu * (uint8_t)(12 < sizeof(str) ? str[12] : 0) +         \
+             0x50c7ac81u * (uint8_t)(13 < sizeof(str) ? str[13] : 0) +         \
+             0x8da473bfu * (uint8_t)(14 < sizeof(str) ? str[14] : 0) +         \
+             0x4f377c01u * (uint8_t)(15 < sizeof(str) ? str[15] : 0) +         \
+             0xfaa8843fu * (uint8_t)(16 < sizeof(str) ? str[16] : 0) +         \
+             0x33b78b81u * (uint8_t)(17 < sizeof(str) ? str[17] : 0) +         \
+             0x45ac54bfu * (uint8_t)(18 < sizeof(str) ? str[18] : 0) +         \
+             0x7a27db01u * (uint8_t)(19 < sizeof(str) ? str[19] : 0) +         \
+             0xeacfe53fu * (uint8_t)(20 < sizeof(str) ? str[20] : 0) +         \
+             0xae686a81u * (uint8_t)(21 < sizeof(str) ? str[21] : 0) +         \
+             0x563335bfu * (uint8_t)(22 < sizeof(str) ? str[22] : 0) +         \
+             0x6c593a01u * (uint8_t)(23 < sizeof(str) ? str[23] : 0) +         \
+             0xe3f6463fu * (uint8_t)(24 < sizeof(str) ? str[24] : 0) +         \
+             0x5fda4981u * (uint8_t)(25 < sizeof(str) ? str[25] : 0) +         \
+             0xe03916bfu * (uint8_t)(26 < sizeof(str) ? str[26] : 0) +         \
+             0x44cb9901u * (uint8_t)(27 < sizeof(str) ? str[27] : 0) +         \
+             0x871ba73fu * (uint8_t)(28 < sizeof(str) ? str[28] : 0) +         \
+             0xe70d2881u * (uint8_t)(29 < sizeof(str) ? str[29] : 0) +         \
+             0x04bdf7bfu * (uint8_t)(30 < sizeof(str) ? str[30] : 0) +         \
+             0x227ef801u * (uint8_t)(31 < sizeof(str) ? str[31] : 0) +         \
+             0x7540083fu * (uint8_t)(32 < sizeof(str) ? str[32] : 0) +         \
+             0xe3010781u * (uint8_t)(33 < sizeof(str) ? str[33] : 0) +         \
+             0xe4c1d8bfu * (uint8_t)(34 < sizeof(str) ? str[34] : 0) +         \
+             0x24735701u * (uint8_t)(35 < sizeof(str) ? str[35] : 0) +         \
+             0x4f63693fu * (uint8_t)(36 < sizeof(str) ? str[36] : 0) +         \
+             0xf2b5e681u * (uint8_t)(37 < sizeof(str) ? str[37] : 0) +         \
+             0xa144b9bfu * (uint8_t)(38 < sizeof(str) ? str[38] : 0) +         \
+             0x69a8b601u * (uint8_t)(39 < sizeof(str) ? str[39] : 0) +         \
+             0xb685ca3fu * (uint8_t)(40 < sizeof(str) ? str[40] : 0) +         \
+             0xb52bc581u * (uint8_t)(41 < sizeof(str) ? str[41] : 0) +         \
+             0x5b469abfu * (uint8_t)(42 < sizeof(str) ? str[42] : 0) +         \
+             0x111f1501u * (uint8_t)(43 < sizeof(str) ? str[43] : 0) +         \
+             0x4ba72b3fu * (uint8_t)(44 < sizeof(str) ? str[44] : 0) +         \
+             0xc962a481u * (uint8_t)(45 < sizeof(str) ? str[45] : 0) +         \
+             0x33c77bbfu * (uint8_t)(46 < sizeof(str) ? str[46] : 0) +         \
+             0x39d67401u * (uint8_t)(47 < sizeof(str) ? str[47] : 0) +         \
+             0xafc78c3fu * (uint8_t)(48 < sizeof(str) ? str[48] : 0) +         \
+             0xce5a8381u * (uint8_t)(49 < sizeof(str) ? str[49] : 0) +         \
+             0x4bc75cbfu * (uint8_t)(50 < sizeof(str) ? str[50] : 0) +         \
+             0x02ced301u * (uint8_t)(51 < sizeof(str) ? str[51] : 0) +         \
+             0x83e6ed3fu * (uint8_t)(52 < sizeof(str) ? str[52] : 0) +         \
+             0x63136281u * (uint8_t)(53 < sizeof(str) ? str[53] : 0) +         \
+             0xc4463dbfu * (uint8_t)(54 < sizeof(str) ? str[54] : 0) +         \
+             0x8b083201u * (uint8_t)(55 < sizeof(str) ? str[55] : 0) +         \
+             0x69054e3fu * (uint8_t)(56 < sizeof(str) ? str[56] : 0) +         \
+             0x268d4181u * (uint8_t)(57 < sizeof(str) ? str[57] : 0) +         \
+             0xbe441ebfu * (uint8_t)(58 < sizeof(str) ? str[58] : 0) +         \
+             0xf1829101u * (uint8_t)(59 < sizeof(str) ? str[59] : 0) +         \
+             0x0022af3fu * (uint8_t)(60 < sizeof(str) ? str[60] : 0) +         \
+             0xb7c82081u * (uint8_t)(61 < sizeof(str) ? str[61] : 0) +         \
+             0x5ac0ffbfu * (uint8_t)(62 < sizeof(str) ? str[62] : 0) +         \
+             0x553df001u * (uint8_t)(63 < sizeof(str) ? str[63] : 0) +         \
+             0xea3f103fu * (uint8_t)(64 < sizeof(str) ? str[64] : 0) +         \
+             0xb5c3ff81u * (uint8_t)(65 < sizeof(str) ? str[65] : 0) +         \
+             0xbabce0bfu * (uint8_t)(66 < sizeof(str) ? str[66] : 0) +         \
+             0xd53a4f01u * (uint8_t)(67 < sizeof(str) ? str[67] : 0) +         \
+             0xc85a713fu * (uint8_t)(68 < sizeof(str) ? str[68] : 0) +         \
+             0xbf80de81u * (uint8_t)(69 < sizeof(str) ? str[69] : 0) +         \
+             0xff37c1bfu * (uint8_t)(70 < sizeof(str) ? str[70] : 0) +         \
+             0x9077ae01u * (uint8_t)(71 < sizeof(str) ? str[71] : 0) +         \
+             0x3b74d23fu * (uint8_t)(72 < sizeof(str) ? str[72] : 0) +         \
+             0x73febd81u * (uint8_t)(73 < sizeof(str) ? str[73] : 0) +         \
+             0x4931a2bfu * (uint8_t)(74 < sizeof(str) ? str[74] : 0) +         \
+             0xa5f60d01u * (uint8_t)(75 < sizeof(str) ? str[75] : 0) +         \
+             0xe48e333fu * (uint8_t)(76 < sizeof(str) ? str[76] : 0) +         \
+             0x723d9c81u * (uint8_t)(77 < sizeof(str) ? str[77] : 0) +         \
+             0xb9aa83bfu * (uint8_t)(78 < sizeof(str) ? str[78] : 0) +         \
+             0x34b56c01u * (uint8_t)(79 < sizeof(str) ? str[79] : 0) +         \
+             0x64a6943fu * (uint8_t)(80 < sizeof(str) ? str[80] : 0) +         \
+             0x593d7b81u * (uint8_t)(81 < sizeof(str) ? str[81] : 0) +         \
+             0x71a264bfu * (uint8_t)(82 < sizeof(str) ? str[82] : 0) +         \
+             0x5bb5cb01u * (uint8_t)(83 < sizeof(str) ? str[83] : 0) +         \
+             0x5cbdf53fu * (uint8_t)(84 < sizeof(str) ? str[84] : 0) +         \
+             0xc7fe5a81u * (uint8_t)(85 < sizeof(str) ? str[85] : 0) +         \
+             0x921945bfu * (uint8_t)(86 < sizeof(str) ? str[86] : 0) +         \
+             0x39f72a01u * (uint8_t)(87 < sizeof(str) ? str[87] : 0) +         \
+             0x6dd4563fu * (uint8_t)(88 < sizeof(str) ? str[88] : 0) +         \
+             0x5d803981u * (uint8_t)(89 < sizeof(str) ? str[89] : 0) +         \
+             0x3c0f26bfu * (uint8_t)(90 < sizeof(str) ? str[90] : 0) +         \
+             0xee798901u * (uint8_t)(91 < sizeof(str) ? str[91] : 0) +         \
+             0x38e9b73fu * (uint8_t)(92 < sizeof(str) ? str[92] : 0) +         \
+             0xb8c31881u * (uint8_t)(93 < sizeof(str) ? str[93] : 0) +         \
+             0x908407bfu * (uint8_t)(94 < sizeof(str) ? str[94] : 0) +         \
+             0x983ce801u * (uint8_t)(95 < sizeof(str) ? str[95] : 0))
+
+// clang-format on
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/tokenize_string.h b/pw_tokenizer/public/pw_tokenizer/internal/tokenize_string.h
new file mode 100644
index 0000000..40e5150
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/internal/tokenize_string.h
@@ -0,0 +1,69 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// Selects the hash macro implementation to use. The implementation selected
+// depends on the language (C or C++) and value of PW_TOKENIZER_CFG_HASH_LENGTH.
+// The options are:
+//
+//   - C++ hash constexpr function, which works for any hash length
+//   - C 80-character hash macro
+//   - C 96-character hash macro
+//   - C 128-character hash macro
+//
+// C hash macros for other lengths may be generated using generate_hash_macro.py
+// and added to this file.
+#pragma once
+
+#include <stdint.h>
+
+#if __cplusplus  // In C++, use a constexpr function to calculate the hash.
+
+#include "pw_tokenizer/pw_tokenizer_65599_fixed_length_hash.h"
+
+#define PW_TOKENIZER_STRING_TOKEN(format)                \
+  pw::tokenizer::PwTokenizer65599FixedLengthHash(        \
+      std::string_view((format), sizeof(format "") - 1), \
+      PW_TOKENIZER_CFG_HASH_LENGTH)
+
+#else  // In C code, use the hashing macro
+
+#if PW_TOKENIZER_CFG_HASH_LENGTH == 80
+
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h"
+#define PW_TOKENIZER_STRING_TOKEN PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH
+
+#elif PW_TOKENIZER_CFG_HASH_LENGTH == 96
+
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h"
+#define PW_TOKENIZER_STRING_TOKEN PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH
+
+#elif PW_TOKENIZER_CFG_HASH_LENGTH == 128
+
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h"
+#define PW_TOKENIZER_STRING_TOKEN PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH
+
+#else  // unsupported hash length
+
+// Only hash lengths for which there is a corresponding macro header
+// (pw_tokenizer/internal/mash_macro_#.h) are supported. Additional macros may
+// be generated with the generate_hash_macro.py function. New macro headers must
+// be added to this file.
+#error "Unsupported value for PW_TOKENIZER_CFG_HASH_LENGTH"
+
+#endif  // PW_TOKENIZER_CFG_HASH_LENGTH
+
+#endif  // __cplusplus
+
+// The type of the token used in place of a format string.
+typedef uint32_t pw_TokenizerStringToken;
diff --git a/pw_tokenizer/public/pw_tokenizer/pw_tokenizer_65599_fixed_length_hash.h b/pw_tokenizer/public/pw_tokenizer/pw_tokenizer_65599_fixed_length_hash.h
new file mode 100644
index 0000000..bd4a140
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/pw_tokenizer_65599_fixed_length_hash.h
@@ -0,0 +1,59 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace pw::tokenizer {
+
+// The constant to use when generating the hash. Changing this changes the value
+// of all hashes, so do not change it randomly.
+inline constexpr uint32_t k65599HashConstant = 65599u;
+
+// Calculates the hash of a string. This function calculates hashes at either
+// runtime or compile time in C++ code.
+//
+// This function only hashes up to a fixed length. Characters beyond that length
+// are ignored. Hashing to a fixed length makes it possible to compute this hash
+// in a preprocessor macro. To eliminate some collisions, the length of the
+// string is hashed as if it were the first character.
+//
+// This hash is calculated with the following equation, where s is the string
+// and k is the maximum hash length:
+//
+//    H(s, k) = len(s) + 65599 * s[0] + 65599^2 * s[1] + ... + 65599^k * s[k-1]
+//
+// The hash algorithm is a modified version of the x65599 hash used by the SDBM
+// open source project. This hash has the following differences from x65599:
+//   - Characters are only hashed up to a fixed maximum string length.
+//   - Characters are hashed in reverse order.
+//   - The string length is hashed as the first character in the string.
+constexpr uint32_t PwTokenizer65599FixedLengthHash(std::string_view string,
+                                                   size_t hash_length) {
+  // The length is hashed as if it were the first character.
+  uint32_t hash = string.size();
+  uint32_t coefficient = k65599HashConstant;
+
+  // Hash all of the characters in the string as unsigned ints.
+  for (uint8_t ch : string.substr(0, hash_length)) {
+    hash += coefficient * ch;
+    coefficient *= k65599HashConstant;
+  }
+
+  return hash;
+}
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/public/pw_tokenizer/token_database.h b/pw_tokenizer/public/pw_tokenizer/token_database.h
new file mode 100644
index 0000000..ae1ebaf
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/token_database.h
@@ -0,0 +1,308 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <iterator>
+
+namespace pw::tokenizer {
+
+// Reads entries from a binary token string database. This class does not copy
+// or modify the contents of the database.
+//
+// A binary token database is comprised of a 16-byte header followed by an array
+// of 8-byte entries and a table of null-terminated strings. The header
+// specifies the number of entries. Each entry contains information about a
+// tokenized string: the token and removal date, if any. All fields are
+// little-endian.
+//
+//            Header
+//            ======
+//   Offset  Size  Field
+//   -----------------------------------
+//        0     6  Magic number (TOKENS)
+//        6     2  Version (00 00)
+//        8     4  Entry count
+//       12     4  Reserved
+//
+//             Entry
+//             =====
+//   Offset  Size  Field
+//   -----------------------------------
+//        0     4  Token
+//        4     4  Removal date (d m yy)
+//
+// Entries are sorted by token. A string table with a null-terminated string for
+// each entry in order follows the entries.
+//
+// Entries are accessed by iterating over the database. A O(n) Find function is
+// also provided. In typical use, a TokenDatabase is preprocessed by a
+// Detokenizer into a std::unordered_map.
+class TokenDatabase {
+ public:
+  // Internal struct that describes how the underlying binary token database
+  // stores entries. RawEntries generally should not be used directly. Instead,
+  // use an Entry, which contains a pointer to the entry's string.
+  struct RawEntry {
+    uint32_t token;
+    uint32_t date_removed;
+  };
+
+  static_assert(sizeof(RawEntry) == 8u);
+
+  // An entry in the token database. This struct adds the string to a RawEntry.
+  struct Entry {
+    // The token calculated for this string.
+    uint32_t token;
+
+    // The date the token and string was removed from the database, or
+    // 0xFFFFFFFF if it was never removed. Dates are encoded such that natural
+    // integer sorting sorts from oldest to newest dates. The day is stored an
+    // an 8-bit day, 8-bit month, and 16-bit year, packed into a little-endian
+    // uint32_t.
+    uint32_t date_removed;
+
+    // The null-terminated string represented by this token.
+    const char* string;
+  };
+
+  // Iterator for TokenDatabase values. Note that this is not a normal STL-style
+  // iterator, since * returns a value instead of a reference.
+  class Iterator {
+   public:
+    constexpr Iterator(const RawEntry* raw_entry, const char* string)
+        : raw_(raw_entry), string_(string) {}
+
+    // Constructs a TokenDatabase::Entry for the entry this iterator refers to.
+    constexpr Entry entry() const {
+      return {raw_->token, raw_->date_removed, string_};
+    }
+
+    constexpr Iterator& operator++() {
+      raw_ += 1;
+      // Move string_ to the character beyond the next null terminator.
+      while (*string_++ != '\0') {
+      }
+      return *this;
+    }
+    constexpr Iterator operator++(int) { return operator++(); }
+    constexpr bool operator==(const Iterator& rhs) const {
+      return raw_ == rhs.raw_;
+    }
+    constexpr bool operator!=(const Iterator& rhs) const {
+      return raw_ != rhs.raw_;
+    }
+
+    // Derefencing a TokenDatabase::Iterator returns an Entry, not an Entry&.
+    constexpr Entry operator*() const { return entry(); }
+
+    // Point directly into the underlying RawEntry. Strings are not accessible
+    // via this operator.
+    constexpr const RawEntry* operator->() const { return raw_; }
+
+    constexpr ptrdiff_t operator-(const Iterator& rhs) const {
+      return raw_ - rhs.raw_;
+    }
+
+   private:
+    const RawEntry* raw_;
+    const char* string_;
+  };
+
+  // A list of token entries returned from a Find operation. This object can be
+  // iterated over or indexed as an array.
+  class Entries {
+   public:
+    constexpr Entries(const Iterator& begin, const Iterator& end)
+        : begin_(begin), end_(end) {}
+
+    // The number of entries in this list.
+    constexpr size_t size() const { return end_ - begin_; }
+
+    // True of the list is empty.
+    constexpr bool empty() const { return begin_ == end_; }
+
+    // Accesses the specified entry in this set. Returns an Entry object, which
+    // is constructed from the underlying raw entry. The index must be less than
+    // size(). This operation is O(n) in size().
+    Entry operator[](size_t index) const;
+
+    constexpr const Iterator& begin() const { return begin_; }
+    constexpr const Iterator& end() const { return end_; }
+
+   private:
+    Iterator begin_;
+    Iterator end_;
+  };
+
+  // Returns true if the provided data is a valid token database. This checks
+  // the magic number ("TOKENS"), version (which must be 0), and that there is
+  // is one string for each entry in the database. A database with extra strings
+  // or other trailing data is considered valid.
+  template <typename ByteArray>
+  static constexpr bool IsValid(const ByteArray& bytes) {
+    return HasValidHeader(bytes) && EachEntryHasAString(bytes);
+  }
+
+  // Creates a TokenDatabase and checks if the provided data is valid at compile
+  // time. Accepts references to constexpr containers (array, span, string_view,
+  // etc.) with static storage duration. For example:
+  //
+  //   constexpr char kMyData[] = ...;
+  //   constexpr TokenDatabase db = TokenDatabase::Create<kMyData>();
+  //
+  template <const auto& kDatabaseBytes>
+  static constexpr TokenDatabase Create() {
+    static_assert(
+        HasValidHeader<decltype(kDatabaseBytes)>(kDatabaseBytes),
+        "Databases must start with a 16-byte header that begins with TOKENS.");
+
+    static_assert(EachEntryHasAString<decltype(kDatabaseBytes)>(kDatabaseBytes),
+                  "The database must have at least one string for each entry.");
+
+    return TokenDatabase(std::data(kDatabaseBytes));
+  }
+
+  // Creates a TokenDatabase from the provided byte array. The array may be a
+  // span, array, or other container type. If the data is not valid, returns a
+  // default-constructed database for which ok() is false.
+  //
+  // Prefer the Create overload that takes the data as a template parameter
+  // whenever possible, since that function checks the integrity of the data at
+  // compile time.
+  template <typename ByteArray>
+  static constexpr TokenDatabase Create(const ByteArray& database_bytes) {
+    return IsValid<ByteArray>(database_bytes)
+               ? TokenDatabase(std::data(database_bytes))
+               : TokenDatabase();  // Invalid database.
+  }
+  // Creates a database with no data. ok() returns false.
+  constexpr TokenDatabase() : begin_{.data = nullptr}, end_{.data = nullptr} {}
+
+  // Returns all entries associated with this token. This is a O(n) operation.
+  Entries Find(uint32_t token) const;
+
+  // Returns the total number of entries (unique token-string pairs).
+  constexpr size_t size() const {
+    return (end_.data - begin_.data) / sizeof(RawEntry);
+  }
+
+  // True if this database was constructed with valid data.
+  constexpr bool ok() const { return begin_.data != nullptr; }
+
+  Iterator begin() const { return Iterator(begin_.entry, end_.data); }
+  Iterator end() const { return Iterator(end_.entry, nullptr); }
+
+ private:
+  struct Header {
+    std::array<char, 6> magic;
+    uint16_t version;
+    uint32_t entry_count;
+    uint32_t reserved;
+  };
+
+  static_assert(sizeof(Header) == 2 * sizeof(RawEntry));
+
+  template <typename ByteArray>
+  static constexpr bool HasValidHeader(const ByteArray& bytes) {
+    static_assert(sizeof(*std::data(bytes)) == 1u);
+
+    if (std::size(bytes) < sizeof(Header)) {
+      return false;
+    }
+
+    // Check the magic number and version.
+    for (size_t i = 0; i < kMagicAndVersion.size(); ++i) {
+      if (bytes[i] != kMagicAndVersion[i]) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  template <typename ByteArray>
+  static constexpr bool EachEntryHasAString(const ByteArray& bytes) {
+    const size_t entries = ReadEntryCount(std::data(bytes));
+
+    // Check that the data is large enough to have a string table.
+    if (std::size(bytes) < StringTable(entries)) {
+      return false;
+    }
+
+    // Count the strings in the string table.
+    size_t string_count = 0;
+    for (auto i = std::begin(bytes) + StringTable(entries); i < std::end(bytes);
+         ++i) {
+      string_count += (*i == '\0') ? 1 : 0;
+    }
+
+    // Check that there is at least one string for each entry.
+    return string_count >= entries;
+  }
+
+  // Reads the number of entries from a database header. Cast to the bytes to
+  // uint8_t to avoid sign extension if T is signed.
+  template <typename T>
+  static constexpr uint32_t ReadEntryCount(const T* header_bytes) {
+    const T* bytes = header_bytes + offsetof(Header, entry_count);
+    return static_cast<uint8_t>(bytes[0]) |
+           static_cast<uint8_t>(bytes[1]) << 8 |
+           static_cast<uint8_t>(bytes[2]) << 16 |
+           static_cast<uint8_t>(bytes[3]) << 24;
+  }
+
+  // Calculates the offset of the string table.
+  static constexpr size_t StringTable(size_t entries) {
+    return sizeof(Header) + entries * sizeof(RawEntry);
+  }
+
+  // The magic number that starts the table is "TOKENS". The version is encoded
+  // next as two bytes.
+  static constexpr std::array<char, 8> kMagicAndVersion = {
+      'T', 'O', 'K', 'E', 'N', 'S', '\0', '\0'};
+
+  template <typename Byte>
+  constexpr TokenDatabase(const Byte bytes[])
+      : TokenDatabase(bytes + sizeof(Header),
+                      bytes + StringTable(ReadEntryCount(bytes))) {
+    static_assert(sizeof(Byte) == 1u);
+  }
+
+  // It is illegal to reinterpret_cast in constexpr functions, but acceptable to
+  // use unions. Instead of using a reinterpret_cast to change the byte pointer
+  // to a RawEntry pointer, have a separate overload for each byte pointer type
+  // and store them in a union.
+  constexpr TokenDatabase(const char* begin, const char* end)
+      : begin_{.data = begin}, end_{.data = end} {}
+
+  constexpr TokenDatabase(const unsigned char* begin, const unsigned char* end)
+      : begin_{.unsigned_data = begin}, end_{.unsigned_data = end} {}
+
+  constexpr TokenDatabase(const signed char* begin, const signed char* end)
+      : begin_{.signed_data = begin}, end_{.signed_data = end} {}
+
+  // Store the beginning and end pointers as a union to avoid breaking constexpr
+  // rules for reinterpret_cast.
+  union {
+    const RawEntry* entry;
+    const char* data;
+    const unsigned char* unsigned_data;
+    const signed char* signed_data;
+  } begin_, end_;
+};
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/public/pw_tokenizer/tokenize.h b/pw_tokenizer/public/pw_tokenizer/tokenize.h
new file mode 100644
index 0000000..e793d16
--- /dev/null
+++ b/pw_tokenizer/public/pw_tokenizer/tokenize.h
@@ -0,0 +1,350 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+#pragma once
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "pw_preprocessor/compiler.h"
+#include "pw_preprocessor/concat.h"
+#include "pw_preprocessor/macro_arg_count.h"
+#include "pw_preprocessor/util.h"
+#include "pw_tokenizer/internal/argument_types.h"
+#include "pw_tokenizer/internal/tokenize_string.h"
+
+// Tokenizes a string literal and converts it to a pw_TokenizerStringToken. This
+// expression can be assigned to a local or global variable, but cannot be used
+// in another expression. For example:
+//
+//   constexpr uint32_t global = PW_TOKENIZE_STRING("Wow!");  // This works.
+//
+//   void SomeFunction() {
+//     constexpr uint32_t token = PW_TOKENIZE_STRING("Cool!");  // This works.
+//
+//     DoSomethingElse(PW_TOKENIZE_STRING("Lame!"));  // This does NOT work.
+//   }
+//
+#define PW_TOKENIZE_STRING(string_literal) \
+  _PW_TOKENIZE_LITERAL_UNIQUE(__COUNTER__, string_literal)
+
+// Encodes a tokenized string and arguments to the provided buffer. The size of
+// the buffer is passed via a pointer to a size_t. After encoding is complete,
+// the size_t is set to the number of bytes written to the buffer.
+//
+// The macro's arguments are equivalent to the following function signature:
+//
+//   TokenizeToBuffer(void* buffer,
+//                    size_t* buffer_size_pointer,
+//                    const char* format,
+//                    ...);  /* printf-style arguments */
+//
+// For example, the following encodes a tokenized string with a temperature to a
+// buffer. The buffer is passed to a function to send the message over a UART.
+//
+//   uint8_t buffer[32];
+//   size_t size_bytes = sizeof(buffer);
+//   PW_TOKENIZE_TO_BUFFER(
+//       buffer, &size_bytes, "Temperature (C): %0.2f", temperature_c);
+//   MyProject_EnqueueMessageForUart(buffer, size);
+//
+#define PW_TOKENIZE_TO_BUFFER(buffer, buffer_size_pointer, format, ...) \
+  do {                                                                  \
+    _PW_TOKENIZE_STRING(format, __VA_ARGS__);                           \
+    pw_TokenizeToBuffer(buffer,                                         \
+                        buffer_size_pointer,                            \
+                        _pw_tokenizer_token,                            \
+                        PW_TOKENIZER_ARG_TYPES(__VA_ARGS__)             \
+                            PW_COMMA_ARGS(__VA_ARGS__));                \
+  } while (0)
+
+// Encodes a tokenized string and arguments to a buffer on the stack. The
+// provided callback is called with the encoded data. The size of the
+// stack-allocated argument encoding buffer is set with the
+// PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES option.
+//
+// The macro's arguments are equivalent to the following function signature:
+//
+//   TokenizeToCallback(void (*callback)(const uint8_t* data, size_t size),
+//                      const char* format,
+//                      ...);  /* printf-style arguments */
+//
+// For example, the following encodes a tokenized string with a sensor name and
+// floating point data. The encoded message is passed directly to the
+// MyProject_EnqueueMessageForUart function, which the caller provides as a
+// callback.
+//
+//   void MyProject_EnqueueMessageForUart(const uint8_t* buffer,
+//                                        size_t size_bytes) {
+//     uart_queue_write(uart_instance, buffer, size_bytes);
+//   }
+//
+//   void LogSensorValue(const char* sensor_name, float value) {
+//     PW_TOKENIZE_TO_CALLBACK(MyProject_EnqueueMessageForUart,
+//                             "%s: %f",
+//                             sensor_name,
+//                             value);
+//   }
+//
+#define PW_TOKENIZE_TO_CALLBACK(callback, format, ...)        \
+  do {                                                        \
+    _PW_TOKENIZE_STRING(format, __VA_ARGS__);                 \
+    pw_TokenizeToCallback(callback,                           \
+                          _pw_tokenizer_token,                \
+                          PW_TOKENIZER_ARG_TYPES(__VA_ARGS__) \
+                              PW_COMMA_ARGS(__VA_ARGS__));    \
+  } while (0)
+
+// Encodes a tokenized string and arguments to a buffer on the stack. The buffer
+// is passed to the user-defined pw_TokenizerHandleEncodedMessage function. The
+// size of the stack-allocated argument encoding buffer is set with the
+// PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES option.
+//
+// The option PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER must be enabled
+// in order to use this macro. When that option is enabled, the
+// pw_TokenizerHandleEncodedMessage function *MUST* be defined by the user of
+// pw_tokenizer.
+//
+// The macro's arguments are equivalent to the following function signature:
+//
+//   TokenizeToGlobalHandler(const char* format,
+//                           ...);  /* printf-style arguments */
+//
+// For example, the following encodes a tokenized string with a value returned
+// from a function call. The encoded message is passed to the caller-defined
+// pw_TokenizerHandleEncodedMessage function.
+//
+//   void OutputLastReadSize() {
+//     PW_TOKENIZE_TO_GLOBAL_HANDLER("Read %u bytes", ReadSizeBytes());
+//   }
+//
+//   void pw_TokenizerHandleEncodedMessage(const uint8_t encoded_message[],
+//                                         size_t size_bytes) {
+//     MyProject_EnqueueMessageForUart(buffer, size_bytes);
+//   }
+//
+#if PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+#define PW_TOKENIZE_TO_GLOBAL_HANDLER(format, ...)                 \
+  do {                                                             \
+    _PW_TOKENIZE_STRING(format, __VA_ARGS__);                      \
+    pw_TokenizeToGlobalHandler(_pw_tokenizer_token,                \
+                               PW_TOKENIZER_ARG_TYPES(__VA_ARGS__) \
+                                   PW_COMMA_ARGS(__VA_ARGS__));    \
+  } while (0)
+
+// If PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER is set, this function
+// must be defined by the user of pw_tokenizer. This function is called with the
+// encoded message by pw_TokenizeToGlobalHandler.
+PW_EXTERN_C void pw_TokenizerHandleEncodedMessage(
+    const uint8_t encoded_message[], size_t size_bytes);
+
+// This function encodes the tokenized strings. Do not call it directly;
+// instead, use the PW_TOKENIZE_TO_GLOBAL_HANDLER macro.
+PW_EXTERN_C void pw_TokenizeToGlobalHandler(pw_TokenizerStringToken token,
+                                            pw_TokenizerArgTypes types,
+                                            ...);
+
+#else  // PW_TOKENIZE_TO_GLOBAL_HANDLER is not available
+
+#define PW_TOKENIZE_TO_GLOBAL_HANDLER(...)                                    \
+  static_assert(0,                                                            \
+                "PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER must be " \
+                "set to 1 to use PW_TOKENIZE_TO_GLOBAL_HANDLER")
+
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+// Like PW_TOKENIZE_TO_GLOBAL_HANDLER, encodes a tokenized string and arguments
+// to a buffer on the stack. The macro adds a payload argument, which is passed
+// through to the global handler function
+// pw_TokenizerHandleEncodedMessageWithPayload, which must be defined by the
+// user of pw_tokenizer. The payload type is specified by the
+// PW_TOKENIZER_CFG_PAYLOAD_TYPE option and defaults to void*.
+//
+// For example, the following tokenizes a log string and passes the log level as
+// the payload.
+/*
+     #define LOG_ERROR(...) \
+         PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(kLogLevelError, __VA_ARGS__)
+
+     void pw_TokenizerHandleEncodedMessageWithPayload(
+         pw_TokenizerPayload log_level,
+         const uint8_t encoded_message[],
+         size_t size_bytes) {
+       if (log_level >= kLogLevelWarning) {
+         MyProject_EnqueueMessageForUart(buffer, size_bytes);
+       }
+     }
+ */
+#if PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+#define PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(payload, format, ...)      \
+  do {                                                                        \
+    _PW_TOKENIZE_STRING(format, __VA_ARGS__);                                 \
+    pw_TokenizeToGlobalHandlerWithPayload(payload,                            \
+                                          _pw_tokenizer_token,                \
+                                          PW_TOKENIZER_ARG_TYPES(__VA_ARGS__) \
+                                              PW_COMMA_ARGS(__VA_ARGS__));    \
+  } while (0)
+
+typedef PW_TOKENIZER_CFG_PAYLOAD_TYPE pw_TokenizerPayload;
+
+// If PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD is set,
+// this function must be defined by the user of pw_tokenizer. This function is
+// called with the encoded message by pw_TokenizeToGlobalHandler and a
+// caller-provided payload argument.
+PW_EXTERN_C void pw_TokenizerHandleEncodedMessageWithPayload(
+    pw_TokenizerPayload payload,
+    const uint8_t encoded_message[],
+    size_t size_bytes);
+
+// This function encodes the tokenized strings. Do not call it directly;
+// instead, use the PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD macro.
+PW_EXTERN_C void pw_TokenizeToGlobalHandlerWithPayload(
+    pw_TokenizerPayload payload,
+    pw_TokenizerStringToken token,
+    pw_TokenizerArgTypes types,
+    ...);
+
+#else  // PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD is not available
+
+#define PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(...)                    \
+  static_assert(0,                                                         \
+                "PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_" \
+                "PAYLOAD must be set to 1 to use "                         \
+                "PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD")
+
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+
+PW_EXTERN_C_START
+
+// These functions encode the tokenized strings. These should not be called
+// directly. Instead, use the corresponding PW_TOKENIZE_TO_* macros above.
+void pw_TokenizeToBuffer(void* buffer,
+                         size_t* buffer_size_bytes,  // input and output arg
+                         pw_TokenizerStringToken token,
+                         pw_TokenizerArgTypes types,
+                         ...);
+
+void pw_TokenizeToCallback(void (*callback)(const uint8_t* encoded_message,
+                                            size_t size_bytes),
+                           pw_TokenizerStringToken token,
+                           pw_TokenizerArgTypes types,
+                           ...);
+
+// This empty function allows the compiler to check the format string.
+inline void pw_TokenizerCheckFormatString(const char* format, ...)
+    PW_PRINTF_FORMAT(1, 2);
+
+inline void pw_TokenizerCheckFormatString(const char* format, ...) {
+  PW_UNUSED(format);
+}
+
+PW_EXTERN_C_END
+
+// These macros implement string tokenization. They should not be used directly;
+// use one of the PW_TOKENIZE_* macros above instead.
+#define _PW_TOKENIZE_LITERAL_UNIQUE(id, string_literal)                     \
+  /* assign to a variable */ PW_TOKENIZER_STRING_TOKEN(string_literal);     \
+                                                                            \
+  /* Declare without nested scope so this works in or out of a function. */ \
+  static _PW_TOKENIZER_CONST char PW_CONCAT(                                \
+      _pw_tokenizer_string_literal_DO_NOT_USE_THIS_VARIABLE_,               \
+      id)[] _PW_TOKENIZER_SECTION(id) = string_literal
+
+// This macro uses __COUNTER__ to generate an identifier to use in the main
+// tokenization macro below. The identifier is unique within a compilation unit.
+#define _PW_TOKENIZE_STRING(format, ...) \
+  _PW_TOKENIZE_STRING_UNIQUE(__COUNTER__, format, __VA_ARGS__)
+
+// This macro takes a printf-style format string and corresponding arguments. It
+// checks that the arguments are correct, stores the format string in a special
+// section, and calculates the string's token at compile time.
+// clang-format off
+#define _PW_TOKENIZE_STRING_UNIQUE(id, format, ...)                            \
+  if (0) { /* Do not execute to prevent double evaluation of the arguments. */ \
+    pw_TokenizerCheckFormatString(format PW_COMMA_ARGS(__VA_ARGS__));          \
+  }                                                                            \
+                                                                               \
+  /* Check that the macro is invoked with a supported number of arguments. */  \
+  static_assert(                                                               \
+      PW_ARG_COUNT(__VA_ARGS__) <= PW_TOKENIZER_MAX_SUPPORTED_ARGS,            \
+      "Tokenized strings cannot have more than "                               \
+      PW_STRINGIFY(PW_TOKENIZER_MAX_SUPPORTED_ARGS) " arguments; "             \
+      PW_STRINGIFY(PW_ARG_COUNT(__VA_ARGS__)) " arguments were used for "      \
+      #format " (" #__VA_ARGS__ ")");                                          \
+                                                                               \
+  /* Declare the format string as an array in the special tokenized string */  \
+  /* section, which should be excluded from the final binary. Use unique   */  \
+  /* names for the section and variable to avoid compiler warnings.        */  \
+  static _PW_TOKENIZER_CONST char PW_CONCAT(                                   \
+      _pw_tokenizer_format_string_, id)[] _PW_TOKENIZER_SECTION(id) = format;  \
+                                                                               \
+  /* Tokenize the string to a pw_TokenizerStringToken at compile time. */      \
+  _PW_TOKENIZER_CONST pw_TokenizerStringToken _pw_tokenizer_token =            \
+      PW_TOKENIZER_STRING_TOKEN(format)
+
+// clang-format on
+
+#ifdef __cplusplus  // use constexpr for C++
+#define _PW_TOKENIZER_CONST constexpr
+#else  // use const for C
+#define _PW_TOKENIZER_CONST const
+#endif  // __cplusplus
+
+// _PW_TOKENIZER_SECTION places the format string in a special .pw_tokenized.#
+// linker section. Host-side decoding tools read the strings from this section
+// to build a database of tokenized strings.
+//
+// This section should be declared as type INFO so that it is excluded from the
+// final binary. To declare the section, as well as the .tokenizer_info section,
+// used for tokenizer metadata, add the following to the linker
+// script's SECTIONS command:
+//
+//   .tokenized 0x00000000 (INFO) :
+//   {
+//     KEEP(*(.tokenized))
+//     KEEP(*(.tokenized.*))
+//   }
+//
+//   .tokenizer_info 0x00000000 (INFO) :
+//   {
+//     KEEP(*(.tokenizer_info))
+//   }
+//
+// Any address could be used for this section, but it should not map to a real
+// device to avoid confusion. 0x00000000 is a reasonable default. An address
+// such as 0xFF000000 that is outside of the ARMv7m memory map could also be
+// used.
+//
+// A linker script snippet that provides these sections is provided in the file
+// tokenizer_linker_sections.ld. This file may be directly included into
+// existing linker scripts.
+//
+// The tokenized string sections can also be managed without linker script
+// modifications, though this is not recommended. The section can be extracted
+// and removed from the ELF with objcopy:
+//
+//   objcopy --only-section .tokenize* <ORIGINAL_ELF> <OUTPUT_ELF>
+//   objcopy --remove-section .tokenize* <ORIGINAL_ELF>
+//
+// OUTPUT_ELF will be an ELF with only the tokenized strings, and the original
+// ELF file will have the sections removed.
+//
+// Without the above linker script modifications, the section garbage collection
+// option (--gc-sections) removes the tokenized string sections. To avoid
+// editing the target linker script, a separate metadata ELF can be linked
+// without --gc-sections to preserve the tokenized data.
+#define _PW_TOKENIZER_SECTION(unique) \
+  PW_KEEP_IN_SECTION(PW_STRINGIFY(PW_CONCAT(.tokenized., unique)))
diff --git a/pw_tokenizer/pw_tokenizer_private/argument_types_test.h b/pw_tokenizer/pw_tokenizer_private/argument_types_test.h
new file mode 100644
index 0000000..52ac6c9
--- /dev/null
+++ b/pw_tokenizer/pw_tokenizer_private/argument_types_test.h
@@ -0,0 +1,46 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+#pragma once
+
+#include <stdint.h>
+
+#include "pw_preprocessor/util.h"
+#include "pw_tokenizer/internal/argument_types.h"
+
+PW_EXTERN_C_START
+
+pw_TokenizerArgTypes pw_TestTokenizerNoArgs(void);
+
+pw_TokenizerArgTypes pw_TestTokenizerChar(void);
+pw_TokenizerArgTypes pw_TestTokenizerUint8(void);
+pw_TokenizerArgTypes pw_TestTokenizerUint16(void);
+pw_TokenizerArgTypes pw_TestTokenizerInt32(void);
+pw_TokenizerArgTypes pw_TestTokenizerInt64(void);
+pw_TokenizerArgTypes pw_TestTokenizerUint64(void);
+pw_TokenizerArgTypes pw_TestTokenizerFloat(void);
+pw_TokenizerArgTypes pw_TestTokenizerDouble(void);
+pw_TokenizerArgTypes pw_TestTokenizerString(void);
+pw_TokenizerArgTypes pw_TestTokenizerMutableString(void);
+
+pw_TokenizerArgTypes pw_TestTokenizerIntFloat(void);
+pw_TokenizerArgTypes pw_TestTokenizerUint64Char(void);
+pw_TokenizerArgTypes pw_TestTokenizerStringString(void);
+pw_TokenizerArgTypes pw_TestTokenizerUint16Int(void);
+pw_TokenizerArgTypes pw_TestTokenizerFloatString(void);
+
+pw_TokenizerArgTypes pw_TestTokenizerNull(void);
+pw_TokenizerArgTypes pw_TestTokenizerPointer(void);
+pw_TokenizerArgTypes pw_TestTokenizerPointerPointer(void);
+
+PW_EXTERN_C_END
diff --git a/pw_tokenizer/pw_tokenizer_private/generated_hash_test_cases.h b/pw_tokenizer/pw_tokenizer_private/generated_hash_test_cases.h
new file mode 100644
index 0000000..d7667fb
--- /dev/null
+++ b/pw_tokenizer/pw_tokenizer_private/generated_hash_test_cases.h
@@ -0,0 +1,908 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// AUTOGENERATED - DO NOT EDIT
+//
+// This file was generated by generate_hash_test_data.py.
+// To make changes, update the script and run it to generate new files.
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h"
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h"
+#include "pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h"
+
+namespace pw::tokenizer {
+
+// Test a series of generated test cases.
+inline constexpr struct {
+  std::string_view string;
+  size_t hash_length;
+  uint32_t python_calculated_hash;
+  uint32_t macro_calculated_hash;  // clang-format off
+} kHashTests[] = {
+
+{
+  std::string_view("", 0u),
+  80u,  // fixed hash length
+  UINT32_C(0),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH(""),  // macro-calculated hash
+},
+{
+  std::string_view("", 0u),
+  96u,  // fixed hash length
+  UINT32_C(0),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH(""),  // macro-calculated hash
+},
+{
+  std::string_view("", 0u),
+  128u,  // fixed hash length
+  UINT32_C(0),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH(""),  // macro-calculated hash
+},
+{
+  std::string_view("\xa1", 1u),
+  80u,  // fixed hash length
+  UINT32_C(10561440),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xa1"),  // macro-calculated hash
+},
+{
+  std::string_view("\xa1", 1u),
+  96u,  // fixed hash length
+  UINT32_C(10561440),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xa1"),  // macro-calculated hash
+},
+{
+  std::string_view("\xa1", 1u),
+  128u,  // fixed hash length
+  UINT32_C(10561440),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xa1"),  // macro-calculated hash
+},
+{
+  std::string_view("\xff", 1u),
+  80u,  // fixed hash length
+  UINT32_C(16727746),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xff"),  // macro-calculated hash
+},
+{
+  std::string_view("\xff", 1u),
+  96u,  // fixed hash length
+  UINT32_C(16727746),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xff"),  // macro-calculated hash
+},
+{
+  std::string_view("\xff", 1u),
+  128u,  // fixed hash length
+  UINT32_C(16727746),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xff"),  // macro-calculated hash
+},
+{
+  std::string_view("\x00", 1u),
+  80u,  // fixed hash length
+  UINT32_C(1),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\x00", 1u),
+  96u,  // fixed hash length
+  UINT32_C(1),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\x00", 1u),
+  128u,  // fixed hash length
+  UINT32_C(1),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\x00\x00", 2u),
+  80u,  // fixed hash length
+  UINT32_C(2),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x00\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\x00\x00", 2u),
+  96u,  // fixed hash length
+  UINT32_C(2),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x00\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\x00\x00", 2u),
+  128u,  // fixed hash length
+  UINT32_C(2),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x00\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("a", 1u),
+  80u,  // fixed hash length
+  UINT32_C(6363104),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("a"),  // macro-calculated hash
+},
+{
+  std::string_view("a", 1u),
+  96u,  // fixed hash length
+  UINT32_C(6363104),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("a"),  // macro-calculated hash
+},
+{
+  std::string_view("a", 1u),
+  128u,  // fixed hash length
+  UINT32_C(6363104),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("a"),  // macro-calculated hash
+},
+{
+  std::string_view("A", 1u),
+  80u,  // fixed hash length
+  UINT32_C(4263936),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("A"),  // macro-calculated hash
+},
+{
+  std::string_view("A", 1u),
+  96u,  // fixed hash length
+  UINT32_C(4263936),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("A"),  // macro-calculated hash
+},
+{
+  std::string_view("A", 1u),
+  128u,  // fixed hash length
+  UINT32_C(4263936),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("A"),  // macro-calculated hash
+},
+{
+  std::string_view("hello, \"world\"", 14u),
+  80u,  // fixed hash length
+  UINT32_C(3537412730),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("hello, \"world\""),  // macro-calculated hash
+},
+{
+  std::string_view("hello, \"world\"", 14u),
+  96u,  // fixed hash length
+  UINT32_C(3537412730),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("hello, \"world\""),  // macro-calculated hash
+},
+{
+  std::string_view("hello, \"world\"", 14u),
+  128u,  // fixed hash length
+  UINT32_C(3537412730),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("hello, \"world\""),  // macro-calculated hash
+},
+{
+  std::string_view("YOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYO", 200u),
+  80u,  // fixed hash length
+  UINT32_C(2035157304),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("YOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYO"),  // macro-calculated hash
+},
+{
+  std::string_view("YOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYO", 200u),
+  96u,  // fixed hash length
+  UINT32_C(4222077672),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("YOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYO"),  // macro-calculated hash
+},
+{
+  std::string_view("YOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYO", 200u),
+  128u,  // fixed hash length
+  UINT32_C(255790664),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("YOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYO"),  // macro-calculated hash
+},
+{
+  std::string_view("4", 1u),
+  80u,  // fixed hash length
+  UINT32_C(3411149),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("4"),  // macro-calculated hash
+},
+{
+  std::string_view("4", 1u),
+  96u,  // fixed hash length
+  UINT32_C(3411149),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("4"),  // macro-calculated hash
+},
+{
+  std::string_view("4", 1u),
+  128u,  // fixed hash length
+  UINT32_C(3411149),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("4"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe0", 1u),
+  80u,  // fixed hash length
+  UINT32_C(14694177),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xe0"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe0", 1u),
+  96u,  // fixed hash length
+  UINT32_C(14694177),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xe0"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe0", 1u),
+  128u,  // fixed hash length
+  UINT32_C(14694177),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xe0"),  // macro-calculated hash
+},
+{
+  std::string_view("\x90\xb9", 2u),
+  80u,  // fixed hash length
+  UINT32_C(1537824683),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x90\xb9"),  // macro-calculated hash
+},
+{
+  std::string_view("\x90\xb9", 2u),
+  96u,  // fixed hash length
+  UINT32_C(1537824683),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x90\xb9"),  // macro-calculated hash
+},
+{
+  std::string_view("\x90\xb9", 2u),
+  128u,  // fixed hash length
+  UINT32_C(1537824683),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x90\xb9"),  // macro-calculated hash
+},
+{
+  std::string_view("\x6a\xe7", 2u),
+  80u,  // fixed hash length
+  UINT32_C(1915361151),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x6a\xe7"),  // macro-calculated hash
+},
+{
+  std::string_view("\x6a\xe7", 2u),
+  96u,  // fixed hash length
+  UINT32_C(1915361151),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x6a\xe7"),  // macro-calculated hash
+},
+{
+  std::string_view("\x6a\xe7", 2u),
+  128u,  // fixed hash length
+  UINT32_C(1915361151),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x6a\xe7"),  // macro-calculated hash
+},
+{
+  std::string_view("dy0", 3u),
+  80u,  // fixed hash length
+  UINT32_C(4114649192),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("dy0"),  // macro-calculated hash
+},
+{
+  std::string_view("dy0", 3u),
+  96u,  // fixed hash length
+  UINT32_C(4114649192),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("dy0"),  // macro-calculated hash
+},
+{
+  std::string_view("dy0", 3u),
+  128u,  // fixed hash length
+  UINT32_C(4114649192),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("dy0"),  // macro-calculated hash
+},
+{
+  std::string_view("\xc4\x18\x32", 3u),
+  80u,  // fixed hash length
+  UINT32_C(585787813),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xc4\x18\x32"),  // macro-calculated hash
+},
+{
+  std::string_view("\xc4\x18\x32", 3u),
+  96u,  // fixed hash length
+  UINT32_C(585787813),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xc4\x18\x32"),  // macro-calculated hash
+},
+{
+  std::string_view("\xc4\x18\x32", 3u),
+  128u,  // fixed hash length
+  UINT32_C(585787813),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xc4\x18\x32"),  // macro-calculated hash
+},
+{
+  std::string_view("\x1c\xfc\x28\x2b", 4u),
+  80u,  // fixed hash length
+  UINT32_C(704109799),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x1c\xfc\x28\x2b"),  // macro-calculated hash
+},
+{
+  std::string_view("\x1c\xfc\x28\x2b", 4u),
+  96u,  // fixed hash length
+  UINT32_C(704109799),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x1c\xfc\x28\x2b"),  // macro-calculated hash
+},
+{
+  std::string_view("\x1c\xfc\x28\x2b", 4u),
+  128u,  // fixed hash length
+  UINT32_C(704109799),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x1c\xfc\x28\x2b"),  // macro-calculated hash
+},
+{
+  std::string_view("\xab\x96\x56\x70", 4u),
+  80u,  // fixed hash length
+  UINT32_C(2738614345),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xab\x96\x56\x70"),  // macro-calculated hash
+},
+{
+  std::string_view("\xab\x96\x56\x70", 4u),
+  96u,  // fixed hash length
+  UINT32_C(2738614345),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xab\x96\x56\x70"),  // macro-calculated hash
+},
+{
+  std::string_view("\xab\x96\x56\x70", 4u),
+  128u,  // fixed hash length
+  UINT32_C(2738614345),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xab\x96\x56\x70"),  // macro-calculated hash
+},
+{
+  std::string_view("\x18\x1e\x6e\x6a\x73", 5u),
+  80u,  // fixed hash length
+  UINT32_C(580554452),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x18\x1e\x6e\x6a\x73"),  // macro-calculated hash
+},
+{
+  std::string_view("\x18\x1e\x6e\x6a\x73", 5u),
+  96u,  // fixed hash length
+  UINT32_C(580554452),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x18\x1e\x6e\x6a\x73"),  // macro-calculated hash
+},
+{
+  std::string_view("\x18\x1e\x6e\x6a\x73", 5u),
+  128u,  // fixed hash length
+  UINT32_C(580554452),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x18\x1e\x6e\x6a\x73"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\xe5\xdf\x22\x00", 5u),
+  80u,  // fixed hash length
+  UINT32_C(4269181327),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xde\xe5\xdf\x22\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\xe5\xdf\x22\x00", 5u),
+  96u,  // fixed hash length
+  UINT32_C(4269181327),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xde\xe5\xdf\x22\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\xe5\xdf\x22\x00", 5u),
+  128u,  // fixed hash length
+  UINT32_C(4269181327),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xde\xe5\xdf\x22\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\x59\xac\x64\x3b\xc7\x36", 6u),
+  80u,  // fixed hash length
+  UINT32_C(2461849503),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x59\xac\x64\x3b\xc7\x36"),  // macro-calculated hash
+},
+{
+  std::string_view("\x59\xac\x64\x3b\xc7\x36", 6u),
+  96u,  // fixed hash length
+  UINT32_C(2461849503),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x59\xac\x64\x3b\xc7\x36"),  // macro-calculated hash
+},
+{
+  std::string_view("\x59\xac\x64\x3b\xc7\x36", 6u),
+  128u,  // fixed hash length
+  UINT32_C(2461849503),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x59\xac\x64\x3b\xc7\x36"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe1\xef\x87\x8d\xbc\xd7", 6u),
+  80u,  // fixed hash length
+  UINT32_C(2407518645),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xe1\xef\x87\x8d\xbc\xd7"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe1\xef\x87\x8d\xbc\xd7", 6u),
+  96u,  // fixed hash length
+  UINT32_C(2407518645),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xe1\xef\x87\x8d\xbc\xd7"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe1\xef\x87\x8d\xbc\xd7", 6u),
+  128u,  // fixed hash length
+  UINT32_C(2407518645),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xe1\xef\x87\x8d\xbc\xd7"),  // macro-calculated hash
+},
+{
+  std::string_view("\x34\xd8\x3a\xbb\xf1\x0e\x07", 7u),
+  80u,  // fixed hash length
+  UINT32_C(2657240642),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x34\xd8\x3a\xbb\xf1\x0e\x07"),  // macro-calculated hash
+},
+{
+  std::string_view("\x34\xd8\x3a\xbb\xf1\x0e\x07", 7u),
+  96u,  // fixed hash length
+  UINT32_C(2657240642),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x34\xd8\x3a\xbb\xf1\x0e\x07"),  // macro-calculated hash
+},
+{
+  std::string_view("\x34\xd8\x3a\xbb\xf1\x0e\x07", 7u),
+  128u,  // fixed hash length
+  UINT32_C(2657240642),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x34\xd8\x3a\xbb\xf1\x0e\x07"),  // macro-calculated hash
+},
+{
+  std::string_view("\xa2\x8e\xb6\x56\x83\xd2\x89", 7u),
+  80u,  // fixed hash length
+  UINT32_C(2016713689),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xa2\x8e\xb6\x56\x83\xd2\x89"),  // macro-calculated hash
+},
+{
+  std::string_view("\xa2\x8e\xb6\x56\x83\xd2\x89", 7u),
+  96u,  // fixed hash length
+  UINT32_C(2016713689),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xa2\x8e\xb6\x56\x83\xd2\x89"),  // macro-calculated hash
+},
+{
+  std::string_view("\xa2\x8e\xb6\x56\x83\xd2\x89", 7u),
+  128u,  // fixed hash length
+  UINT32_C(2016713689),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xa2\x8e\xb6\x56\x83\xd2\x89"),  // macro-calculated hash
+},
+{
+  std::string_view("\x20\x3b\x66\x3f\x80\x8b\xd6\x9f", 8u),
+  80u,  // fixed hash length
+  UINT32_C(727179216),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x20\x3b\x66\x3f\x80\x8b\xd6\x9f"),  // macro-calculated hash
+},
+{
+  std::string_view("\x20\x3b\x66\x3f\x80\x8b\xd6\x9f", 8u),
+  96u,  // fixed hash length
+  UINT32_C(727179216),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x20\x3b\x66\x3f\x80\x8b\xd6\x9f"),  // macro-calculated hash
+},
+{
+  std::string_view("\x20\x3b\x66\x3f\x80\x8b\xd6\x9f", 8u),
+  128u,  // fixed hash length
+  UINT32_C(727179216),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x20\x3b\x66\x3f\x80\x8b\xd6\x9f"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe5\x15\xbf\x96\x52\xd8\x22\x72", 8u),
+  80u,  // fixed hash length
+  UINT32_C(110264805),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xe5\x15\xbf\x96\x52\xd8\x22\x72"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe5\x15\xbf\x96\x52\xd8\x22\x72", 8u),
+  96u,  // fixed hash length
+  UINT32_C(110264805),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xe5\x15\xbf\x96\x52\xd8\x22\x72"),  // macro-calculated hash
+},
+{
+  std::string_view("\xe5\x15\xbf\x96\x52\xd8\x22\x72", 8u),
+  128u,  // fixed hash length
+  UINT32_C(110264805),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xe5\x15\xbf\x96\x52\xd8\x22\x72"),  // macro-calculated hash
+},
+{
+  std::string_view("\x21\x5a\x75\x73\xf1\x70\xc1\x0e\x82", 9u),
+  80u,  // fixed hash length
+  UINT32_C(261914122),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x21\x5a\x75\x73\xf1\x70\xc1\x0e\x82"),  // macro-calculated hash
+},
+{
+  std::string_view("\x21\x5a\x75\x73\xf1\x70\xc1\x0e\x82", 9u),
+  96u,  // fixed hash length
+  UINT32_C(261914122),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x21\x5a\x75\x73\xf1\x70\xc1\x0e\x82"),  // macro-calculated hash
+},
+{
+  std::string_view("\x21\x5a\x75\x73\xf1\x70\xc1\x0e\x82", 9u),
+  128u,  // fixed hash length
+  UINT32_C(261914122),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x21\x5a\x75\x73\xf1\x70\xc1\x0e\x82"),  // macro-calculated hash
+},
+{
+  std::string_view("\x37\x1b\xf3\x87\x5c\xd9\x94\xc6\x40", 9u),
+  80u,  // fixed hash length
+  UINT32_C(1833718768),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x37\x1b\xf3\x87\x5c\xd9\x94\xc6\x40"),  // macro-calculated hash
+},
+{
+  std::string_view("\x37\x1b\xf3\x87\x5c\xd9\x94\xc6\x40", 9u),
+  96u,  // fixed hash length
+  UINT32_C(1833718768),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x37\x1b\xf3\x87\x5c\xd9\x94\xc6\x40"),  // macro-calculated hash
+},
+{
+  std::string_view("\x37\x1b\xf3\x87\x5c\xd9\x94\xc6\x40", 9u),
+  128u,  // fixed hash length
+  UINT32_C(1833718768),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x37\x1b\xf3\x87\x5c\xd9\x94\xc6\x40"),  // macro-calculated hash
+},
+{
+  std::string_view("\x71\x48\x39\xc6\x53\x98\xfa\xc6\x54\x3d", 10u),
+  80u,  // fixed hash length
+  UINT32_C(2326646568),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x71\x48\x39\xc6\x53\x98\xfa\xc6\x54\x3d"),  // macro-calculated hash
+},
+{
+  std::string_view("\x71\x48\x39\xc6\x53\x98\xfa\xc6\x54\x3d", 10u),
+  96u,  // fixed hash length
+  UINT32_C(2326646568),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x71\x48\x39\xc6\x53\x98\xfa\xc6\x54\x3d"),  // macro-calculated hash
+},
+{
+  std::string_view("\x71\x48\x39\xc6\x53\x98\xfa\xc6\x54\x3d", 10u),
+  128u,  // fixed hash length
+  UINT32_C(2326646568),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x71\x48\x39\xc6\x53\x98\xfa\xc6\x54\x3d"),  // macro-calculated hash
+},
+{
+  std::string_view("\x82\x26\x3a\x43\x83\xcf\x86\x3d\x3b\xf5", 10u),
+  80u,  // fixed hash length
+  UINT32_C(2712532084),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x82\x26\x3a\x43\x83\xcf\x86\x3d\x3b\xf5"),  // macro-calculated hash
+},
+{
+  std::string_view("\x82\x26\x3a\x43\x83\xcf\x86\x3d\x3b\xf5", 10u),
+  96u,  // fixed hash length
+  UINT32_C(2712532084),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x82\x26\x3a\x43\x83\xcf\x86\x3d\x3b\xf5"),  // macro-calculated hash
+},
+{
+  std::string_view("\x82\x26\x3a\x43\x83\xcf\x86\x3d\x3b\xf5", 10u),
+  128u,  // fixed hash length
+  UINT32_C(2712532084),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x82\x26\x3a\x43\x83\xcf\x86\x3d\x3b\xf5"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\x35\x78\x6e\x3f\x98\x61\x43\x53\x28\x24", 11u),
+  80u,  // fixed hash length
+  UINT32_C(544632964),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xde\x35\x78\x6e\x3f\x98\x61\x43\x53\x28\x24"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\x35\x78\x6e\x3f\x98\x61\x43\x53\x28\x24", 11u),
+  96u,  // fixed hash length
+  UINT32_C(544632964),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xde\x35\x78\x6e\x3f\x98\x61\x43\x53\x28\x24"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\x35\x78\x6e\x3f\x98\x61\x43\x53\x28\x24", 11u),
+  128u,  // fixed hash length
+  UINT32_C(544632964),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xde\x35\x78\x6e\x3f\x98\x61\x43\x53\x28\x24"),  // macro-calculated hash
+},
+{
+  std::string_view("\x28\x5a\xef\x49\x5c\xfb\x43\x91\xdd\x27\x00", 11u),
+  80u,  // fixed hash length
+  UINT32_C(3878380686),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x28\x5a\xef\x49\x5c\xfb\x43\x91\xdd\x27\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\x28\x5a\xef\x49\x5c\xfb\x43\x91\xdd\x27\x00", 11u),
+  96u,  // fixed hash length
+  UINT32_C(3878380686),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x28\x5a\xef\x49\x5c\xfb\x43\x91\xdd\x27\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\x28\x5a\xef\x49\x5c\xfb\x43\x91\xdd\x27\x00", 11u),
+  128u,  // fixed hash length
+  UINT32_C(3878380686),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x28\x5a\xef\x49\x5c\xfb\x43\x91\xdd\x27\x00"),  // macro-calculated hash
+},
+{
+  std::string_view("\xca\x45\x01\x88\x5d\xf2\x24\xa9\x78\xbf\x91\x97", 12u),
+  80u,  // fixed hash length
+  UINT32_C(4053891765),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xca\x45\x01\x88\x5d\xf2\x24\xa9\x78\xbf\x91\x97"),  // macro-calculated hash
+},
+{
+  std::string_view("\xca\x45\x01\x88\x5d\xf2\x24\xa9\x78\xbf\x91\x97", 12u),
+  96u,  // fixed hash length
+  UINT32_C(4053891765),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xca\x45\x01\x88\x5d\xf2\x24\xa9\x78\xbf\x91\x97"),  // macro-calculated hash
+},
+{
+  std::string_view("\xca\x45\x01\x88\x5d\xf2\x24\xa9\x78\xbf\x91\x97", 12u),
+  128u,  // fixed hash length
+  UINT32_C(4053891765),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xca\x45\x01\x88\x5d\xf2\x24\xa9\x78\xbf\x91\x97"),  // macro-calculated hash
+},
+{
+  std::string_view("\x43\xa1\xfb\x5c\x60\x89\xaf\x2b\xdb\xa9\xe5\x59", 12u),
+  80u,  // fixed hash length
+  UINT32_C(2009683698),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x43\xa1\xfb\x5c\x60\x89\xaf\x2b\xdb\xa9\xe5\x59"),  // macro-calculated hash
+},
+{
+  std::string_view("\x43\xa1\xfb\x5c\x60\x89\xaf\x2b\xdb\xa9\xe5\x59", 12u),
+  96u,  // fixed hash length
+  UINT32_C(2009683698),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x43\xa1\xfb\x5c\x60\x89\xaf\x2b\xdb\xa9\xe5\x59"),  // macro-calculated hash
+},
+{
+  std::string_view("\x43\xa1\xfb\x5c\x60\x89\xaf\x2b\xdb\xa9\xe5\x59", 12u),
+  128u,  // fixed hash length
+  UINT32_C(2009683698),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x43\xa1\xfb\x5c\x60\x89\xaf\x2b\xdb\xa9\xe5\x59"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\x1a\x80\x72\x19\x63\x71\x85\x6c\x53\x51\x7a\x26", 13u),
+  80u,  // fixed hash length
+  UINT32_C(3862326851),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xde\x1a\x80\x72\x19\x63\x71\x85\x6c\x53\x51\x7a\x26"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\x1a\x80\x72\x19\x63\x71\x85\x6c\x53\x51\x7a\x26", 13u),
+  96u,  // fixed hash length
+  UINT32_C(3862326851),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xde\x1a\x80\x72\x19\x63\x71\x85\x6c\x53\x51\x7a\x26"),  // macro-calculated hash
+},
+{
+  std::string_view("\xde\x1a\x80\x72\x19\x63\x71\x85\x6c\x53\x51\x7a\x26", 13u),
+  128u,  // fixed hash length
+  UINT32_C(3862326851),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xde\x1a\x80\x72\x19\x63\x71\x85\x6c\x53\x51\x7a\x26"),  // macro-calculated hash
+},
+{
+  std::string_view("\x59\x74\xd1\xa5\x70\x0b\xef\x7d\x45\xa9\xcc\xef\x1e", 13u),
+  80u,  // fixed hash length
+  UINT32_C(2358079886),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x59\x74\xd1\xa5\x70\x0b\xef\x7d\x45\xa9\xcc\xef\x1e"),  // macro-calculated hash
+},
+{
+  std::string_view("\x59\x74\xd1\xa5\x70\x0b\xef\x7d\x45\xa9\xcc\xef\x1e", 13u),
+  96u,  // fixed hash length
+  UINT32_C(2358079886),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x59\x74\xd1\xa5\x70\x0b\xef\x7d\x45\xa9\xcc\xef\x1e"),  // macro-calculated hash
+},
+{
+  std::string_view("\x59\x74\xd1\xa5\x70\x0b\xef\x7d\x45\xa9\xcc\xef\x1e", 13u),
+  128u,  // fixed hash length
+  UINT32_C(2358079886),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x59\x74\xd1\xa5\x70\x0b\xef\x7d\x45\xa9\xcc\xef\x1e"),  // macro-calculated hash
+},
+{
+  std::string_view("\xed\xf2\x0a\x96\x1e\xec\x9e\xda\x71\xba\x60\x4d\x49\x8a", 14u),
+  80u,  // fixed hash length
+  UINT32_C(4215296608),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xed\xf2\x0a\x96\x1e\xec\x9e\xda\x71\xba\x60\x4d\x49\x8a"),  // macro-calculated hash
+},
+{
+  std::string_view("\xed\xf2\x0a\x96\x1e\xec\x9e\xda\x71\xba\x60\x4d\x49\x8a", 14u),
+  96u,  // fixed hash length
+  UINT32_C(4215296608),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xed\xf2\x0a\x96\x1e\xec\x9e\xda\x71\xba\x60\x4d\x49\x8a"),  // macro-calculated hash
+},
+{
+  std::string_view("\xed\xf2\x0a\x96\x1e\xec\x9e\xda\x71\xba\x60\x4d\x49\x8a", 14u),
+  128u,  // fixed hash length
+  UINT32_C(4215296608),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xed\xf2\x0a\x96\x1e\xec\x9e\xda\x71\xba\x60\x4d\x49\x8a"),  // macro-calculated hash
+},
+{
+  std::string_view("\x89\x5f\xe4\x0a\xfb\x75\xff\x6a\x24\x1c\x06\xec\xad\xc8", 14u),
+  80u,  // fixed hash length
+  UINT32_C(1051337960),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x89\x5f\xe4\x0a\xfb\x75\xff\x6a\x24\x1c\x06\xec\xad\xc8"),  // macro-calculated hash
+},
+{
+  std::string_view("\x89\x5f\xe4\x0a\xfb\x75\xff\x6a\x24\x1c\x06\xec\xad\xc8", 14u),
+  96u,  // fixed hash length
+  UINT32_C(1051337960),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x89\x5f\xe4\x0a\xfb\x75\xff\x6a\x24\x1c\x06\xec\xad\xc8"),  // macro-calculated hash
+},
+{
+  std::string_view("\x89\x5f\xe4\x0a\xfb\x75\xff\x6a\x24\x1c\x06\xec\xad\xc8", 14u),
+  128u,  // fixed hash length
+  UINT32_C(1051337960),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x89\x5f\xe4\x0a\xfb\x75\xff\x6a\x24\x1c\x06\xec\xad\xc8"),  // macro-calculated hash
+},
+{
+  std::string_view("\x63\xe4\xd0\xdd\xf5\x83\xdb\xa4\x6b\x25\xc2\x2f\x8e\xfe\x1a", 15u),
+  80u,  // fixed hash length
+  UINT32_C(3916582129),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x63\xe4\xd0\xdd\xf5\x83\xdb\xa4\x6b\x25\xc2\x2f\x8e\xfe\x1a"),  // macro-calculated hash
+},
+{
+  std::string_view("\x63\xe4\xd0\xdd\xf5\x83\xdb\xa4\x6b\x25\xc2\x2f\x8e\xfe\x1a", 15u),
+  96u,  // fixed hash length
+  UINT32_C(3916582129),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x63\xe4\xd0\xdd\xf5\x83\xdb\xa4\x6b\x25\xc2\x2f\x8e\xfe\x1a"),  // macro-calculated hash
+},
+{
+  std::string_view("\x63\xe4\xd0\xdd\xf5\x83\xdb\xa4\x6b\x25\xc2\x2f\x8e\xfe\x1a", 15u),
+  128u,  // fixed hash length
+  UINT32_C(3916582129),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x63\xe4\xd0\xdd\xf5\x83\xdb\xa4\x6b\x25\xc2\x2f\x8e\xfe\x1a"),  // macro-calculated hash
+},
+{
+  std::string_view("\x12\xbf\x0e\x44\x14\x1d\x31\x3c\x77\x5e\xf9\xa4\x98\x42\x76", 15u),
+  80u,  // fixed hash length
+  UINT32_C(2665036172),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x12\xbf\x0e\x44\x14\x1d\x31\x3c\x77\x5e\xf9\xa4\x98\x42\x76"),  // macro-calculated hash
+},
+{
+  std::string_view("\x12\xbf\x0e\x44\x14\x1d\x31\x3c\x77\x5e\xf9\xa4\x98\x42\x76", 15u),
+  96u,  // fixed hash length
+  UINT32_C(2665036172),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x12\xbf\x0e\x44\x14\x1d\x31\x3c\x77\x5e\xf9\xa4\x98\x42\x76"),  // macro-calculated hash
+},
+{
+  std::string_view("\x12\xbf\x0e\x44\x14\x1d\x31\x3c\x77\x5e\xf9\xa4\x98\x42\x76", 15u),
+  128u,  // fixed hash length
+  UINT32_C(2665036172),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x12\xbf\x0e\x44\x14\x1d\x31\x3c\x77\x5e\xf9\xa4\x98\x42\x76"),  // macro-calculated hash
+},
+{
+  std::string_view("\x97\xec\x22\xd5\x2d\xdb\xd7\x6a\xd7\x80\xae\xd1\x68\x3a\xca\xbe\x6b\x3c\xbb\x1b\x68\xca\xb4\xde\xa0\xb3\x7f\x80\x44\xd2\xa3\xe9\x80\x56\x06\xb9\xe4\xeb\xde\xe2\x9f\xc5\xcd\xc0\x21\x19\x21\x77\xdc\x38\xf9\x6c\xdb\x00\x64\x46\x40\xfa\x29\xd4\x9c\x87\x3e\x80\xd6\xbe\x4e\xed\x12\x54\xa9\x38\xe3\xff\x6f\x79\x30\xd7\xeb", 79u),
+  80u,  // fixed hash length
+  UINT32_C(2352453932),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x97\xec\x22\xd5\x2d\xdb\xd7\x6a\xd7\x80\xae\xd1\x68\x3a\xca\xbe\x6b\x3c\xbb\x1b\x68\xca\xb4\xde\xa0\xb3\x7f\x80\x44\xd2\xa3\xe9\x80\x56\x06\xb9\xe4\xeb\xde\xe2\x9f\xc5\xcd\xc0\x21\x19\x21\x77\xdc\x38\xf9\x6c\xdb\x00\x64\x46\x40\xfa\x29\xd4\x9c\x87\x3e\x80\xd6\xbe\x4e\xed\x12\x54\xa9\x38\xe3\xff\x6f\x79\x30\xd7\xeb"),  // macro-calculated hash
+},
+{
+  std::string_view("\x97\xec\x22\xd5\x2d\xdb\xd7\x6a\xd7\x80\xae\xd1\x68\x3a\xca\xbe\x6b\x3c\xbb\x1b\x68\xca\xb4\xde\xa0\xb3\x7f\x80\x44\xd2\xa3\xe9\x80\x56\x06\xb9\xe4\xeb\xde\xe2\x9f\xc5\xcd\xc0\x21\x19\x21\x77\xdc\x38\xf9\x6c\xdb\x00\x64\x46\x40\xfa\x29\xd4\x9c\x87\x3e\x80\xd6\xbe\x4e\xed\x12\x54\xa9\x38\xe3\xff\x6f\x79\x30\xd7\xeb", 79u),
+  96u,  // fixed hash length
+  UINT32_C(2352453932),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x97\xec\x22\xd5\x2d\xdb\xd7\x6a\xd7\x80\xae\xd1\x68\x3a\xca\xbe\x6b\x3c\xbb\x1b\x68\xca\xb4\xde\xa0\xb3\x7f\x80\x44\xd2\xa3\xe9\x80\x56\x06\xb9\xe4\xeb\xde\xe2\x9f\xc5\xcd\xc0\x21\x19\x21\x77\xdc\x38\xf9\x6c\xdb\x00\x64\x46\x40\xfa\x29\xd4\x9c\x87\x3e\x80\xd6\xbe\x4e\xed\x12\x54\xa9\x38\xe3\xff\x6f\x79\x30\xd7\xeb"),  // macro-calculated hash
+},
+{
+  std::string_view("\x97\xec\x22\xd5\x2d\xdb\xd7\x6a\xd7\x80\xae\xd1\x68\x3a\xca\xbe\x6b\x3c\xbb\x1b\x68\xca\xb4\xde\xa0\xb3\x7f\x80\x44\xd2\xa3\xe9\x80\x56\x06\xb9\xe4\xeb\xde\xe2\x9f\xc5\xcd\xc0\x21\x19\x21\x77\xdc\x38\xf9\x6c\xdb\x00\x64\x46\x40\xfa\x29\xd4\x9c\x87\x3e\x80\xd6\xbe\x4e\xed\x12\x54\xa9\x38\xe3\xff\x6f\x79\x30\xd7\xeb", 79u),
+  128u,  // fixed hash length
+  UINT32_C(2352453932),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x97\xec\x22\xd5\x2d\xdb\xd7\x6a\xd7\x80\xae\xd1\x68\x3a\xca\xbe\x6b\x3c\xbb\x1b\x68\xca\xb4\xde\xa0\xb3\x7f\x80\x44\xd2\xa3\xe9\x80\x56\x06\xb9\xe4\xeb\xde\xe2\x9f\xc5\xcd\xc0\x21\x19\x21\x77\xdc\x38\xf9\x6c\xdb\x00\x64\x46\x40\xfa\x29\xd4\x9c\x87\x3e\x80\xd6\xbe\x4e\xed\x12\x54\xa9\x38\xe3\xff\x6f\x79\x30\xd7\xeb"),  // macro-calculated hash
+},
+{
+  std::string_view("\x4a\x94\x21\xd0\xe7\xa0\xd4\x23\x23\x38\xc1\x04\xce\xbb\xc9\xe6\xcc\x08\x90\x33\x7b\x0a\x28\x31\xf9\xe3\xe6\xc9\x78\x47\x10\x2c\xda\xb3\xf8\xcd\x7a\x20\xb7\xd1\xd6\x4c\xba\x18\x31\x44\x57\x08\x97\x89\xfd\x43\xce\xf2\x06\x67\xa1\x6d\x15\x47\xa1\xe1\x52\xf6\x4a\x9e\x79\x31\xae\x12\xae\xdd\x4b\x3c\x20\xba\xce\x50\x1e\x13", 80u),
+  80u,  // fixed hash length
+  UINT32_C(4169625832),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x4a\x94\x21\xd0\xe7\xa0\xd4\x23\x23\x38\xc1\x04\xce\xbb\xc9\xe6\xcc\x08\x90\x33\x7b\x0a\x28\x31\xf9\xe3\xe6\xc9\x78\x47\x10\x2c\xda\xb3\xf8\xcd\x7a\x20\xb7\xd1\xd6\x4c\xba\x18\x31\x44\x57\x08\x97\x89\xfd\x43\xce\xf2\x06\x67\xa1\x6d\x15\x47\xa1\xe1\x52\xf6\x4a\x9e\x79\x31\xae\x12\xae\xdd\x4b\x3c\x20\xba\xce\x50\x1e\x13"),  // macro-calculated hash
+},
+{
+  std::string_view("\x4a\x94\x21\xd0\xe7\xa0\xd4\x23\x23\x38\xc1\x04\xce\xbb\xc9\xe6\xcc\x08\x90\x33\x7b\x0a\x28\x31\xf9\xe3\xe6\xc9\x78\x47\x10\x2c\xda\xb3\xf8\xcd\x7a\x20\xb7\xd1\xd6\x4c\xba\x18\x31\x44\x57\x08\x97\x89\xfd\x43\xce\xf2\x06\x67\xa1\x6d\x15\x47\xa1\xe1\x52\xf6\x4a\x9e\x79\x31\xae\x12\xae\xdd\x4b\x3c\x20\xba\xce\x50\x1e\x13", 80u),
+  96u,  // fixed hash length
+  UINT32_C(4169625832),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x4a\x94\x21\xd0\xe7\xa0\xd4\x23\x23\x38\xc1\x04\xce\xbb\xc9\xe6\xcc\x08\x90\x33\x7b\x0a\x28\x31\xf9\xe3\xe6\xc9\x78\x47\x10\x2c\xda\xb3\xf8\xcd\x7a\x20\xb7\xd1\xd6\x4c\xba\x18\x31\x44\x57\x08\x97\x89\xfd\x43\xce\xf2\x06\x67\xa1\x6d\x15\x47\xa1\xe1\x52\xf6\x4a\x9e\x79\x31\xae\x12\xae\xdd\x4b\x3c\x20\xba\xce\x50\x1e\x13"),  // macro-calculated hash
+},
+{
+  std::string_view("\x4a\x94\x21\xd0\xe7\xa0\xd4\x23\x23\x38\xc1\x04\xce\xbb\xc9\xe6\xcc\x08\x90\x33\x7b\x0a\x28\x31\xf9\xe3\xe6\xc9\x78\x47\x10\x2c\xda\xb3\xf8\xcd\x7a\x20\xb7\xd1\xd6\x4c\xba\x18\x31\x44\x57\x08\x97\x89\xfd\x43\xce\xf2\x06\x67\xa1\x6d\x15\x47\xa1\xe1\x52\xf6\x4a\x9e\x79\x31\xae\x12\xae\xdd\x4b\x3c\x20\xba\xce\x50\x1e\x13", 80u),
+  128u,  // fixed hash length
+  UINT32_C(4169625832),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x4a\x94\x21\xd0\xe7\xa0\xd4\x23\x23\x38\xc1\x04\xce\xbb\xc9\xe6\xcc\x08\x90\x33\x7b\x0a\x28\x31\xf9\xe3\xe6\xc9\x78\x47\x10\x2c\xda\xb3\xf8\xcd\x7a\x20\xb7\xd1\xd6\x4c\xba\x18\x31\x44\x57\x08\x97\x89\xfd\x43\xce\xf2\x06\x67\xa1\x6d\x15\x47\xa1\xe1\x52\xf6\x4a\x9e\x79\x31\xae\x12\xae\xdd\x4b\x3c\x20\xba\xce\x50\x1e\x13"),  // macro-calculated hash
+},
+{
+  std::string_view("\x79\x1d\xba\x71\x02\x36\xfd\xaf\xbe\x49\x5e\x0b\x77\x7b\x57\xf7\x8b\xad\x6a\xe3\xc5\x57\x5a\x34\xa6\x12\xb2\xb2\x8a\x4e\x11\x13\xa5\x97\x2f\xf6\xbc\x62\xdb\x63\x0b\xa4\xc3\x3d\x66\x92\x3e\x8b\x53\x47\x12\x3a\x36\x9a\xe2\x31\xf9\x0d\x62\x71\x79\x3f\xa7\x04\x09\x8c\x40\xa7\x8a\x17\x3a\xb3\x6a\xea\x51\xdf\x91\x5a\x1d\x42\x4e", 81u),
+  80u,  // fixed hash length
+  UINT32_C(2417296923),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x79\x1d\xba\x71\x02\x36\xfd\xaf\xbe\x49\x5e\x0b\x77\x7b\x57\xf7\x8b\xad\x6a\xe3\xc5\x57\x5a\x34\xa6\x12\xb2\xb2\x8a\x4e\x11\x13\xa5\x97\x2f\xf6\xbc\x62\xdb\x63\x0b\xa4\xc3\x3d\x66\x92\x3e\x8b\x53\x47\x12\x3a\x36\x9a\xe2\x31\xf9\x0d\x62\x71\x79\x3f\xa7\x04\x09\x8c\x40\xa7\x8a\x17\x3a\xb3\x6a\xea\x51\xdf\x91\x5a\x1d\x42\x4e"),  // macro-calculated hash
+},
+{
+  std::string_view("\x79\x1d\xba\x71\x02\x36\xfd\xaf\xbe\x49\x5e\x0b\x77\x7b\x57\xf7\x8b\xad\x6a\xe3\xc5\x57\x5a\x34\xa6\x12\xb2\xb2\x8a\x4e\x11\x13\xa5\x97\x2f\xf6\xbc\x62\xdb\x63\x0b\xa4\xc3\x3d\x66\x92\x3e\x8b\x53\x47\x12\x3a\x36\x9a\xe2\x31\xf9\x0d\x62\x71\x79\x3f\xa7\x04\x09\x8c\x40\xa7\x8a\x17\x3a\xb3\x6a\xea\x51\xdf\x91\x5a\x1d\x42\x4e", 81u),
+  96u,  // fixed hash length
+  UINT32_C(987115853),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x79\x1d\xba\x71\x02\x36\xfd\xaf\xbe\x49\x5e\x0b\x77\x7b\x57\xf7\x8b\xad\x6a\xe3\xc5\x57\x5a\x34\xa6\x12\xb2\xb2\x8a\x4e\x11\x13\xa5\x97\x2f\xf6\xbc\x62\xdb\x63\x0b\xa4\xc3\x3d\x66\x92\x3e\x8b\x53\x47\x12\x3a\x36\x9a\xe2\x31\xf9\x0d\x62\x71\x79\x3f\xa7\x04\x09\x8c\x40\xa7\x8a\x17\x3a\xb3\x6a\xea\x51\xdf\x91\x5a\x1d\x42\x4e"),  // macro-calculated hash
+},
+{
+  std::string_view("\x79\x1d\xba\x71\x02\x36\xfd\xaf\xbe\x49\x5e\x0b\x77\x7b\x57\xf7\x8b\xad\x6a\xe3\xc5\x57\x5a\x34\xa6\x12\xb2\xb2\x8a\x4e\x11\x13\xa5\x97\x2f\xf6\xbc\x62\xdb\x63\x0b\xa4\xc3\x3d\x66\x92\x3e\x8b\x53\x47\x12\x3a\x36\x9a\xe2\x31\xf9\x0d\x62\x71\x79\x3f\xa7\x04\x09\x8c\x40\xa7\x8a\x17\x3a\xb3\x6a\xea\x51\xdf\x91\x5a\x1d\x42\x4e", 81u),
+  128u,  // fixed hash length
+  UINT32_C(987115853),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x79\x1d\xba\x71\x02\x36\xfd\xaf\xbe\x49\x5e\x0b\x77\x7b\x57\xf7\x8b\xad\x6a\xe3\xc5\x57\x5a\x34\xa6\x12\xb2\xb2\x8a\x4e\x11\x13\xa5\x97\x2f\xf6\xbc\x62\xdb\x63\x0b\xa4\xc3\x3d\x66\x92\x3e\x8b\x53\x47\x12\x3a\x36\x9a\xe2\x31\xf9\x0d\x62\x71\x79\x3f\xa7\x04\x09\x8c\x40\xa7\x8a\x17\x3a\xb3\x6a\xea\x51\xdf\x91\x5a\x1d\x42\x4e"),  // macro-calculated hash
+},
+{
+  std::string_view("\x08\xd5\x5f\x9b\x1a\xd5\x15\x4b\x80\x3f\x01\x35\x6f\xda\xf3\x9a\x2d\x8d\xb6\xb2\x36\x8b\xc4\x69\x46\xfe\xe1\x3f\x83\xbc\x45\xc8\x53\x75\xf5\x89\x22\x8b\x14\xfa\xd0\xce\xc9\x85\xe8\x98\x6b\x47\xc4\xa5\xf9\x06\x4c\x39\xdc\x8c\xe2\xf1\xa4\x59\x1c\xc1\xd4\x16\xb1\xb4\x2a\x61\x2c\x48\x2c\x7f\xd2\x1f\x77\xd2\x92\xf9\xfa\x84\x8c\x74\xc7\xa1\x3d\x72\x46\x97\x63\xc1\x97\x9a\x4b\xb2\x17", 95u),
+  80u,  // fixed hash length
+  UINT32_C(1750895817),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x08\xd5\x5f\x9b\x1a\xd5\x15\x4b\x80\x3f\x01\x35\x6f\xda\xf3\x9a\x2d\x8d\xb6\xb2\x36\x8b\xc4\x69\x46\xfe\xe1\x3f\x83\xbc\x45\xc8\x53\x75\xf5\x89\x22\x8b\x14\xfa\xd0\xce\xc9\x85\xe8\x98\x6b\x47\xc4\xa5\xf9\x06\x4c\x39\xdc\x8c\xe2\xf1\xa4\x59\x1c\xc1\xd4\x16\xb1\xb4\x2a\x61\x2c\x48\x2c\x7f\xd2\x1f\x77\xd2\x92\xf9\xfa\x84\x8c\x74\xc7\xa1\x3d\x72\x46\x97\x63\xc1\x97\x9a\x4b\xb2\x17"),  // macro-calculated hash
+},
+{
+  std::string_view("\x08\xd5\x5f\x9b\x1a\xd5\x15\x4b\x80\x3f\x01\x35\x6f\xda\xf3\x9a\x2d\x8d\xb6\xb2\x36\x8b\xc4\x69\x46\xfe\xe1\x3f\x83\xbc\x45\xc8\x53\x75\xf5\x89\x22\x8b\x14\xfa\xd0\xce\xc9\x85\xe8\x98\x6b\x47\xc4\xa5\xf9\x06\x4c\x39\xdc\x8c\xe2\xf1\xa4\x59\x1c\xc1\xd4\x16\xb1\xb4\x2a\x61\x2c\x48\x2c\x7f\xd2\x1f\x77\xd2\x92\xf9\xfa\x84\x8c\x74\xc7\xa1\x3d\x72\x46\x97\x63\xc1\x97\x9a\x4b\xb2\x17", 95u),
+  96u,  // fixed hash length
+  UINT32_C(720276802),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x08\xd5\x5f\x9b\x1a\xd5\x15\x4b\x80\x3f\x01\x35\x6f\xda\xf3\x9a\x2d\x8d\xb6\xb2\x36\x8b\xc4\x69\x46\xfe\xe1\x3f\x83\xbc\x45\xc8\x53\x75\xf5\x89\x22\x8b\x14\xfa\xd0\xce\xc9\x85\xe8\x98\x6b\x47\xc4\xa5\xf9\x06\x4c\x39\xdc\x8c\xe2\xf1\xa4\x59\x1c\xc1\xd4\x16\xb1\xb4\x2a\x61\x2c\x48\x2c\x7f\xd2\x1f\x77\xd2\x92\xf9\xfa\x84\x8c\x74\xc7\xa1\x3d\x72\x46\x97\x63\xc1\x97\x9a\x4b\xb2\x17"),  // macro-calculated hash
+},
+{
+  std::string_view("\x08\xd5\x5f\x9b\x1a\xd5\x15\x4b\x80\x3f\x01\x35\x6f\xda\xf3\x9a\x2d\x8d\xb6\xb2\x36\x8b\xc4\x69\x46\xfe\xe1\x3f\x83\xbc\x45\xc8\x53\x75\xf5\x89\x22\x8b\x14\xfa\xd0\xce\xc9\x85\xe8\x98\x6b\x47\xc4\xa5\xf9\x06\x4c\x39\xdc\x8c\xe2\xf1\xa4\x59\x1c\xc1\xd4\x16\xb1\xb4\x2a\x61\x2c\x48\x2c\x7f\xd2\x1f\x77\xd2\x92\xf9\xfa\x84\x8c\x74\xc7\xa1\x3d\x72\x46\x97\x63\xc1\x97\x9a\x4b\xb2\x17", 95u),
+  128u,  // fixed hash length
+  UINT32_C(720276802),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x08\xd5\x5f\x9b\x1a\xd5\x15\x4b\x80\x3f\x01\x35\x6f\xda\xf3\x9a\x2d\x8d\xb6\xb2\x36\x8b\xc4\x69\x46\xfe\xe1\x3f\x83\xbc\x45\xc8\x53\x75\xf5\x89\x22\x8b\x14\xfa\xd0\xce\xc9\x85\xe8\x98\x6b\x47\xc4\xa5\xf9\x06\x4c\x39\xdc\x8c\xe2\xf1\xa4\x59\x1c\xc1\xd4\x16\xb1\xb4\x2a\x61\x2c\x48\x2c\x7f\xd2\x1f\x77\xd2\x92\xf9\xfa\x84\x8c\x74\xc7\xa1\x3d\x72\x46\x97\x63\xc1\x97\x9a\x4b\xb2\x17"),  // macro-calculated hash
+},
+{
+  std::string_view("\x9b\xf2\x2d\xc5\x5f\xe7\xa6\xf3\xf6\xd8\x2c\x7f\x89\x72\x1d\xba\x88\x1a\x84\xb1\x7b\xad\x24\x96\x31\x80\x10\x2b\x1f\x32\x06\xc8\xef\x00\x5a\xe2\x9c\xfc\x3a\x6f\x5d\x70\xc0\x06\xe0\x8b\xcd\xd5\xec\xf4\x25\x91\xd9\xe4\x86\x4f\x3a\xdb\x36\x42\xde\x57\x8d\x5b\xeb\xd3\x67\x47\x99\x0b\x1b\x26\xd1\x06\x93\x5e\xa2\xf9\xc3\x28\x2e\x51\xed\x99\x12\x84\xd8\x79\x85\x12\x16\xde\x1d\xdc\x47\x4b", 96u),
+  80u,  // fixed hash length
+  UINT32_C(760136888),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x9b\xf2\x2d\xc5\x5f\xe7\xa6\xf3\xf6\xd8\x2c\x7f\x89\x72\x1d\xba\x88\x1a\x84\xb1\x7b\xad\x24\x96\x31\x80\x10\x2b\x1f\x32\x06\xc8\xef\x00\x5a\xe2\x9c\xfc\x3a\x6f\x5d\x70\xc0\x06\xe0\x8b\xcd\xd5\xec\xf4\x25\x91\xd9\xe4\x86\x4f\x3a\xdb\x36\x42\xde\x57\x8d\x5b\xeb\xd3\x67\x47\x99\x0b\x1b\x26\xd1\x06\x93\x5e\xa2\xf9\xc3\x28\x2e\x51\xed\x99\x12\x84\xd8\x79\x85\x12\x16\xde\x1d\xdc\x47\x4b"),  // macro-calculated hash
+},
+{
+  std::string_view("\x9b\xf2\x2d\xc5\x5f\xe7\xa6\xf3\xf6\xd8\x2c\x7f\x89\x72\x1d\xba\x88\x1a\x84\xb1\x7b\xad\x24\x96\x31\x80\x10\x2b\x1f\x32\x06\xc8\xef\x00\x5a\xe2\x9c\xfc\x3a\x6f\x5d\x70\xc0\x06\xe0\x8b\xcd\xd5\xec\xf4\x25\x91\xd9\xe4\x86\x4f\x3a\xdb\x36\x42\xde\x57\x8d\x5b\xeb\xd3\x67\x47\x99\x0b\x1b\x26\xd1\x06\x93\x5e\xa2\xf9\xc3\x28\x2e\x51\xed\x99\x12\x84\xd8\x79\x85\x12\x16\xde\x1d\xdc\x47\x4b", 96u),
+  96u,  // fixed hash length
+  UINT32_C(1408671026),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x9b\xf2\x2d\xc5\x5f\xe7\xa6\xf3\xf6\xd8\x2c\x7f\x89\x72\x1d\xba\x88\x1a\x84\xb1\x7b\xad\x24\x96\x31\x80\x10\x2b\x1f\x32\x06\xc8\xef\x00\x5a\xe2\x9c\xfc\x3a\x6f\x5d\x70\xc0\x06\xe0\x8b\xcd\xd5\xec\xf4\x25\x91\xd9\xe4\x86\x4f\x3a\xdb\x36\x42\xde\x57\x8d\x5b\xeb\xd3\x67\x47\x99\x0b\x1b\x26\xd1\x06\x93\x5e\xa2\xf9\xc3\x28\x2e\x51\xed\x99\x12\x84\xd8\x79\x85\x12\x16\xde\x1d\xdc\x47\x4b"),  // macro-calculated hash
+},
+{
+  std::string_view("\x9b\xf2\x2d\xc5\x5f\xe7\xa6\xf3\xf6\xd8\x2c\x7f\x89\x72\x1d\xba\x88\x1a\x84\xb1\x7b\xad\x24\x96\x31\x80\x10\x2b\x1f\x32\x06\xc8\xef\x00\x5a\xe2\x9c\xfc\x3a\x6f\x5d\x70\xc0\x06\xe0\x8b\xcd\xd5\xec\xf4\x25\x91\xd9\xe4\x86\x4f\x3a\xdb\x36\x42\xde\x57\x8d\x5b\xeb\xd3\x67\x47\x99\x0b\x1b\x26\xd1\x06\x93\x5e\xa2\xf9\xc3\x28\x2e\x51\xed\x99\x12\x84\xd8\x79\x85\x12\x16\xde\x1d\xdc\x47\x4b", 96u),
+  128u,  // fixed hash length
+  UINT32_C(1408671026),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x9b\xf2\x2d\xc5\x5f\xe7\xa6\xf3\xf6\xd8\x2c\x7f\x89\x72\x1d\xba\x88\x1a\x84\xb1\x7b\xad\x24\x96\x31\x80\x10\x2b\x1f\x32\x06\xc8\xef\x00\x5a\xe2\x9c\xfc\x3a\x6f\x5d\x70\xc0\x06\xe0\x8b\xcd\xd5\xec\xf4\x25\x91\xd9\xe4\x86\x4f\x3a\xdb\x36\x42\xde\x57\x8d\x5b\xeb\xd3\x67\x47\x99\x0b\x1b\x26\xd1\x06\x93\x5e\xa2\xf9\xc3\x28\x2e\x51\xed\x99\x12\x84\xd8\x79\x85\x12\x16\xde\x1d\xdc\x47\x4b"),  // macro-calculated hash
+},
+{
+  std::string_view("\xa7\x97\xb4\x6c\x4b\x3e\xa9\x40\x2d\x1c\x46\xd6\x42\xf7\xee\xd3\xc4\xa7\xa8\xbd\xd3\xe0\x1a\x56\x31\x4e\x9b\xbd\x28\x16\x56\x1f\x38\x57\x56\x8b\x7d\xa4\xc9\xe4\xb2\xce\x3c\xf8\x0f\x13\x83\x35\x66\x86\xdf\x33\xfa\x6e\x09\xf7\x3c\x05\xd9\x05\xb3\xb6\x62\xc2\xd9\x75\x00\x7f\x00\xd9\x2c\x67\x78\x8c\x4c\x45\x3b\x9b\xc7\xaf\x6e\xdf\x23\x79\x09\xa4\xbb\x29\x29\x64\xd4\xc7\x2c\x50\x83\x24\xc7", 97u),
+  80u,  // fixed hash length
+  UINT32_C(4113347769),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xa7\x97\xb4\x6c\x4b\x3e\xa9\x40\x2d\x1c\x46\xd6\x42\xf7\xee\xd3\xc4\xa7\xa8\xbd\xd3\xe0\x1a\x56\x31\x4e\x9b\xbd\x28\x16\x56\x1f\x38\x57\x56\x8b\x7d\xa4\xc9\xe4\xb2\xce\x3c\xf8\x0f\x13\x83\x35\x66\x86\xdf\x33\xfa\x6e\x09\xf7\x3c\x05\xd9\x05\xb3\xb6\x62\xc2\xd9\x75\x00\x7f\x00\xd9\x2c\x67\x78\x8c\x4c\x45\x3b\x9b\xc7\xaf\x6e\xdf\x23\x79\x09\xa4\xbb\x29\x29\x64\xd4\xc7\x2c\x50\x83\x24\xc7"),  // macro-calculated hash
+},
+{
+  std::string_view("\xa7\x97\xb4\x6c\x4b\x3e\xa9\x40\x2d\x1c\x46\xd6\x42\xf7\xee\xd3\xc4\xa7\xa8\xbd\xd3\xe0\x1a\x56\x31\x4e\x9b\xbd\x28\x16\x56\x1f\x38\x57\x56\x8b\x7d\xa4\xc9\xe4\xb2\xce\x3c\xf8\x0f\x13\x83\x35\x66\x86\xdf\x33\xfa\x6e\x09\xf7\x3c\x05\xd9\x05\xb3\xb6\x62\xc2\xd9\x75\x00\x7f\x00\xd9\x2c\x67\x78\x8c\x4c\x45\x3b\x9b\xc7\xaf\x6e\xdf\x23\x79\x09\xa4\xbb\x29\x29\x64\xd4\xc7\x2c\x50\x83\x24\xc7", 97u),
+  96u,  // fixed hash length
+  UINT32_C(1367119804),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xa7\x97\xb4\x6c\x4b\x3e\xa9\x40\x2d\x1c\x46\xd6\x42\xf7\xee\xd3\xc4\xa7\xa8\xbd\xd3\xe0\x1a\x56\x31\x4e\x9b\xbd\x28\x16\x56\x1f\x38\x57\x56\x8b\x7d\xa4\xc9\xe4\xb2\xce\x3c\xf8\x0f\x13\x83\x35\x66\x86\xdf\x33\xfa\x6e\x09\xf7\x3c\x05\xd9\x05\xb3\xb6\x62\xc2\xd9\x75\x00\x7f\x00\xd9\x2c\x67\x78\x8c\x4c\x45\x3b\x9b\xc7\xaf\x6e\xdf\x23\x79\x09\xa4\xbb\x29\x29\x64\xd4\xc7\x2c\x50\x83\x24\xc7"),  // macro-calculated hash
+},
+{
+  std::string_view("\xa7\x97\xb4\x6c\x4b\x3e\xa9\x40\x2d\x1c\x46\xd6\x42\xf7\xee\xd3\xc4\xa7\xa8\xbd\xd3\xe0\x1a\x56\x31\x4e\x9b\xbd\x28\x16\x56\x1f\x38\x57\x56\x8b\x7d\xa4\xc9\xe4\xb2\xce\x3c\xf8\x0f\x13\x83\x35\x66\x86\xdf\x33\xfa\x6e\x09\xf7\x3c\x05\xd9\x05\xb3\xb6\x62\xc2\xd9\x75\x00\x7f\x00\xd9\x2c\x67\x78\x8c\x4c\x45\x3b\x9b\xc7\xaf\x6e\xdf\x23\x79\x09\xa4\xbb\x29\x29\x64\xd4\xc7\x2c\x50\x83\x24\xc7", 97u),
+  128u,  // fixed hash length
+  UINT32_C(687960245),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xa7\x97\xb4\x6c\x4b\x3e\xa9\x40\x2d\x1c\x46\xd6\x42\xf7\xee\xd3\xc4\xa7\xa8\xbd\xd3\xe0\x1a\x56\x31\x4e\x9b\xbd\x28\x16\x56\x1f\x38\x57\x56\x8b\x7d\xa4\xc9\xe4\xb2\xce\x3c\xf8\x0f\x13\x83\x35\x66\x86\xdf\x33\xfa\x6e\x09\xf7\x3c\x05\xd9\x05\xb3\xb6\x62\xc2\xd9\x75\x00\x7f\x00\xd9\x2c\x67\x78\x8c\x4c\x45\x3b\x9b\xc7\xaf\x6e\xdf\x23\x79\x09\xa4\xbb\x29\x29\x64\xd4\xc7\x2c\x50\x83\x24\xc7"),  // macro-calculated hash
+},
+{
+  std::string_view("\xf1\xdf\xb7\x7c\xcb\xf7\xc8\xe8\x94\xd3\x04\x33\x3e\x1a\x9c\x7d\x4a\xa4\xc3\xaf\x87\x97\xdb\xcc\xd6\x23\xe5\x0d\xf2\xa9\x9e\x63\xf6\xc4\xa8\x4d\x56\x86\x2b\x9c\x5f\xca\x4c\x17\x88\xa4\x5c\x73\x80\x6b\x2c\x03\x0e\xcd\x9f\xb8\x99\x44\x40\x2a\x33\x73\x94\xd1\x3f\x64\x56\x76\xf1\x9f\xfc\xb4\x1b\x1c\xa7\xc8\x28\x90\x03\x21\xe1\xcf\xb4\x57\x14\x2b\x5f\xbb\x84\x99\x4f\x16\xd4\x25\xe4\x54\x6f\xcd\x2c\x0f\x70\x98\xdb\xf3\xb0\xfe\xe0\x1a\x2e\x3d\x8b\x26\xdf\x28\x41\x16\x21\xc4\x86\x43\x9a\x29\x2b\xa3\x18\x74\x98", 127u),
+  80u,  // fixed hash length
+  UINT32_C(1288060573),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\xf1\xdf\xb7\x7c\xcb\xf7\xc8\xe8\x94\xd3\x04\x33\x3e\x1a\x9c\x7d\x4a\xa4\xc3\xaf\x87\x97\xdb\xcc\xd6\x23\xe5\x0d\xf2\xa9\x9e\x63\xf6\xc4\xa8\x4d\x56\x86\x2b\x9c\x5f\xca\x4c\x17\x88\xa4\x5c\x73\x80\x6b\x2c\x03\x0e\xcd\x9f\xb8\x99\x44\x40\x2a\x33\x73\x94\xd1\x3f\x64\x56\x76\xf1\x9f\xfc\xb4\x1b\x1c\xa7\xc8\x28\x90\x03\x21\xe1\xcf\xb4\x57\x14\x2b\x5f\xbb\x84\x99\x4f\x16\xd4\x25\xe4\x54\x6f\xcd\x2c\x0f\x70\x98\xdb\xf3\xb0\xfe\xe0\x1a\x2e\x3d\x8b\x26\xdf\x28\x41\x16\x21\xc4\x86\x43\x9a\x29\x2b\xa3\x18\x74\x98"),  // macro-calculated hash
+},
+{
+  std::string_view("\xf1\xdf\xb7\x7c\xcb\xf7\xc8\xe8\x94\xd3\x04\x33\x3e\x1a\x9c\x7d\x4a\xa4\xc3\xaf\x87\x97\xdb\xcc\xd6\x23\xe5\x0d\xf2\xa9\x9e\x63\xf6\xc4\xa8\x4d\x56\x86\x2b\x9c\x5f\xca\x4c\x17\x88\xa4\x5c\x73\x80\x6b\x2c\x03\x0e\xcd\x9f\xb8\x99\x44\x40\x2a\x33\x73\x94\xd1\x3f\x64\x56\x76\xf1\x9f\xfc\xb4\x1b\x1c\xa7\xc8\x28\x90\x03\x21\xe1\xcf\xb4\x57\x14\x2b\x5f\xbb\x84\x99\x4f\x16\xd4\x25\xe4\x54\x6f\xcd\x2c\x0f\x70\x98\xdb\xf3\xb0\xfe\xe0\x1a\x2e\x3d\x8b\x26\xdf\x28\x41\x16\x21\xc4\x86\x43\x9a\x29\x2b\xa3\x18\x74\x98", 127u),
+  96u,  // fixed hash length
+  UINT32_C(1810369278),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\xf1\xdf\xb7\x7c\xcb\xf7\xc8\xe8\x94\xd3\x04\x33\x3e\x1a\x9c\x7d\x4a\xa4\xc3\xaf\x87\x97\xdb\xcc\xd6\x23\xe5\x0d\xf2\xa9\x9e\x63\xf6\xc4\xa8\x4d\x56\x86\x2b\x9c\x5f\xca\x4c\x17\x88\xa4\x5c\x73\x80\x6b\x2c\x03\x0e\xcd\x9f\xb8\x99\x44\x40\x2a\x33\x73\x94\xd1\x3f\x64\x56\x76\xf1\x9f\xfc\xb4\x1b\x1c\xa7\xc8\x28\x90\x03\x21\xe1\xcf\xb4\x57\x14\x2b\x5f\xbb\x84\x99\x4f\x16\xd4\x25\xe4\x54\x6f\xcd\x2c\x0f\x70\x98\xdb\xf3\xb0\xfe\xe0\x1a\x2e\x3d\x8b\x26\xdf\x28\x41\x16\x21\xc4\x86\x43\x9a\x29\x2b\xa3\x18\x74\x98"),  // macro-calculated hash
+},
+{
+  std::string_view("\xf1\xdf\xb7\x7c\xcb\xf7\xc8\xe8\x94\xd3\x04\x33\x3e\x1a\x9c\x7d\x4a\xa4\xc3\xaf\x87\x97\xdb\xcc\xd6\x23\xe5\x0d\xf2\xa9\x9e\x63\xf6\xc4\xa8\x4d\x56\x86\x2b\x9c\x5f\xca\x4c\x17\x88\xa4\x5c\x73\x80\x6b\x2c\x03\x0e\xcd\x9f\xb8\x99\x44\x40\x2a\x33\x73\x94\xd1\x3f\x64\x56\x76\xf1\x9f\xfc\xb4\x1b\x1c\xa7\xc8\x28\x90\x03\x21\xe1\xcf\xb4\x57\x14\x2b\x5f\xbb\x84\x99\x4f\x16\xd4\x25\xe4\x54\x6f\xcd\x2c\x0f\x70\x98\xdb\xf3\xb0\xfe\xe0\x1a\x2e\x3d\x8b\x26\xdf\x28\x41\x16\x21\xc4\x86\x43\x9a\x29\x2b\xa3\x18\x74\x98", 127u),
+  128u,  // fixed hash length
+  UINT32_C(2429195322),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\xf1\xdf\xb7\x7c\xcb\xf7\xc8\xe8\x94\xd3\x04\x33\x3e\x1a\x9c\x7d\x4a\xa4\xc3\xaf\x87\x97\xdb\xcc\xd6\x23\xe5\x0d\xf2\xa9\x9e\x63\xf6\xc4\xa8\x4d\x56\x86\x2b\x9c\x5f\xca\x4c\x17\x88\xa4\x5c\x73\x80\x6b\x2c\x03\x0e\xcd\x9f\xb8\x99\x44\x40\x2a\x33\x73\x94\xd1\x3f\x64\x56\x76\xf1\x9f\xfc\xb4\x1b\x1c\xa7\xc8\x28\x90\x03\x21\xe1\xcf\xb4\x57\x14\x2b\x5f\xbb\x84\x99\x4f\x16\xd4\x25\xe4\x54\x6f\xcd\x2c\x0f\x70\x98\xdb\xf3\xb0\xfe\xe0\x1a\x2e\x3d\x8b\x26\xdf\x28\x41\x16\x21\xc4\x86\x43\x9a\x29\x2b\xa3\x18\x74\x98"),  // macro-calculated hash
+},
+{
+  std::string_view("\x43\xd7\x5e\xff\x9b\x29\x34\x97\x50\x92\xb3\xcd\x38\xee\x3c\xdf\xc7\x7a\x76\x4e\x72\xaf\xb2\xdd\x37\x1c\x8e\x1c\xec\x08\x15\x72\x4f\xac\x2d\x67\x19\xc1\xc5\x6f\x52\x7d\x79\xe9\xa8\x3d\xcb\x3b\xdb\x4d\x29\x81\x89\xa8\x1f\xcb\xbf\xc6\x2f\x5a\xab\x9e\x05\xc6\xec\x42\x24\xf0\x9e\x2c\xb4\x7e\xc1\x85\x10\x3c\xc0\xd5\x99\x9a\x62\x52\x04\xde\xe1\xd1\x44\x5e\x4b\x4b\xc1\x10\xf1\xbe\x06\xc8\xf7\x07\xd2\x92\xc8\x92\xb3\xb6\xa5\x79\x6e\x93\x6d\xfa\xd1\x68\x6e\xec\x25\x10\xcf\x4f\x8e\xd4\xfb\x3c\x56\x04\xbc\x6f\xbc\x65", 128u),
+  80u,  // fixed hash length
+  UINT32_C(1125261758),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x43\xd7\x5e\xff\x9b\x29\x34\x97\x50\x92\xb3\xcd\x38\xee\x3c\xdf\xc7\x7a\x76\x4e\x72\xaf\xb2\xdd\x37\x1c\x8e\x1c\xec\x08\x15\x72\x4f\xac\x2d\x67\x19\xc1\xc5\x6f\x52\x7d\x79\xe9\xa8\x3d\xcb\x3b\xdb\x4d\x29\x81\x89\xa8\x1f\xcb\xbf\xc6\x2f\x5a\xab\x9e\x05\xc6\xec\x42\x24\xf0\x9e\x2c\xb4\x7e\xc1\x85\x10\x3c\xc0\xd5\x99\x9a\x62\x52\x04\xde\xe1\xd1\x44\x5e\x4b\x4b\xc1\x10\xf1\xbe\x06\xc8\xf7\x07\xd2\x92\xc8\x92\xb3\xb6\xa5\x79\x6e\x93\x6d\xfa\xd1\x68\x6e\xec\x25\x10\xcf\x4f\x8e\xd4\xfb\x3c\x56\x04\xbc\x6f\xbc\x65"),  // macro-calculated hash
+},
+{
+  std::string_view("\x43\xd7\x5e\xff\x9b\x29\x34\x97\x50\x92\xb3\xcd\x38\xee\x3c\xdf\xc7\x7a\x76\x4e\x72\xaf\xb2\xdd\x37\x1c\x8e\x1c\xec\x08\x15\x72\x4f\xac\x2d\x67\x19\xc1\xc5\x6f\x52\x7d\x79\xe9\xa8\x3d\xcb\x3b\xdb\x4d\x29\x81\x89\xa8\x1f\xcb\xbf\xc6\x2f\x5a\xab\x9e\x05\xc6\xec\x42\x24\xf0\x9e\x2c\xb4\x7e\xc1\x85\x10\x3c\xc0\xd5\x99\x9a\x62\x52\x04\xde\xe1\xd1\x44\x5e\x4b\x4b\xc1\x10\xf1\xbe\x06\xc8\xf7\x07\xd2\x92\xc8\x92\xb3\xb6\xa5\x79\x6e\x93\x6d\xfa\xd1\x68\x6e\xec\x25\x10\xcf\x4f\x8e\xd4\xfb\x3c\x56\x04\xbc\x6f\xbc\x65", 128u),
+  96u,  // fixed hash length
+  UINT32_C(1477867120),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x43\xd7\x5e\xff\x9b\x29\x34\x97\x50\x92\xb3\xcd\x38\xee\x3c\xdf\xc7\x7a\x76\x4e\x72\xaf\xb2\xdd\x37\x1c\x8e\x1c\xec\x08\x15\x72\x4f\xac\x2d\x67\x19\xc1\xc5\x6f\x52\x7d\x79\xe9\xa8\x3d\xcb\x3b\xdb\x4d\x29\x81\x89\xa8\x1f\xcb\xbf\xc6\x2f\x5a\xab\x9e\x05\xc6\xec\x42\x24\xf0\x9e\x2c\xb4\x7e\xc1\x85\x10\x3c\xc0\xd5\x99\x9a\x62\x52\x04\xde\xe1\xd1\x44\x5e\x4b\x4b\xc1\x10\xf1\xbe\x06\xc8\xf7\x07\xd2\x92\xc8\x92\xb3\xb6\xa5\x79\x6e\x93\x6d\xfa\xd1\x68\x6e\xec\x25\x10\xcf\x4f\x8e\xd4\xfb\x3c\x56\x04\xbc\x6f\xbc\x65"),  // macro-calculated hash
+},
+{
+  std::string_view("\x43\xd7\x5e\xff\x9b\x29\x34\x97\x50\x92\xb3\xcd\x38\xee\x3c\xdf\xc7\x7a\x76\x4e\x72\xaf\xb2\xdd\x37\x1c\x8e\x1c\xec\x08\x15\x72\x4f\xac\x2d\x67\x19\xc1\xc5\x6f\x52\x7d\x79\xe9\xa8\x3d\xcb\x3b\xdb\x4d\x29\x81\x89\xa8\x1f\xcb\xbf\xc6\x2f\x5a\xab\x9e\x05\xc6\xec\x42\x24\xf0\x9e\x2c\xb4\x7e\xc1\x85\x10\x3c\xc0\xd5\x99\x9a\x62\x52\x04\xde\xe1\xd1\x44\x5e\x4b\x4b\xc1\x10\xf1\xbe\x06\xc8\xf7\x07\xd2\x92\xc8\x92\xb3\xb6\xa5\x79\x6e\x93\x6d\xfa\xd1\x68\x6e\xec\x25\x10\xcf\x4f\x8e\xd4\xfb\x3c\x56\x04\xbc\x6f\xbc\x65", 128u),
+  128u,  // fixed hash length
+  UINT32_C(3694995364),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x43\xd7\x5e\xff\x9b\x29\x34\x97\x50\x92\xb3\xcd\x38\xee\x3c\xdf\xc7\x7a\x76\x4e\x72\xaf\xb2\xdd\x37\x1c\x8e\x1c\xec\x08\x15\x72\x4f\xac\x2d\x67\x19\xc1\xc5\x6f\x52\x7d\x79\xe9\xa8\x3d\xcb\x3b\xdb\x4d\x29\x81\x89\xa8\x1f\xcb\xbf\xc6\x2f\x5a\xab\x9e\x05\xc6\xec\x42\x24\xf0\x9e\x2c\xb4\x7e\xc1\x85\x10\x3c\xc0\xd5\x99\x9a\x62\x52\x04\xde\xe1\xd1\x44\x5e\x4b\x4b\xc1\x10\xf1\xbe\x06\xc8\xf7\x07\xd2\x92\xc8\x92\xb3\xb6\xa5\x79\x6e\x93\x6d\xfa\xd1\x68\x6e\xec\x25\x10\xcf\x4f\x8e\xd4\xfb\x3c\x56\x04\xbc\x6f\xbc\x65"),  // macro-calculated hash
+},
+{
+  std::string_view("\x23\x66\x8c\x45\x63\x79\xfb\x31\x74\x7c\xa5\xcc\x58\x09\xc6\x46\x13\x9e\xf1\x96\x66\xb5\x03\x3a\xaa\x03\x78\x6e\x93\xdb\x9e\x70\x74\x05\xaa\x30\xca\x00\xf3\xc3\xef\xd0\x2e\xb5\xc3\x6c\xbd\x7f\xbc\x41\xd2\x1a\x83\xec\x72\xb0\x8c\x35\x9e\xcf\x6f\x16\x90\x6b\xdb\xa5\x88\xc6\xdc\x05\x8c\xdf\x51\x99\xa7\xca\xa9\xe3\x59\x68\x20\xb2\xdd\x63\xab\xd9\x27\xec\xcb\x1a\x2c\xac\xed\x5d\x8c\x97\x04\xbe\x22\x76\xbb\x14\x3b\x5f\xff\xc3\x78\xe2\xed\x79\xea\xdf\xc4\x5a\x28\xf2\x7a\xcb\x20\x68\x7b\xdc\xbf\xf2\x77\x61\x56\x5a\xe1", 129u),
+  80u,  // fixed hash length
+  UINT32_C(6281856),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH("\x23\x66\x8c\x45\x63\x79\xfb\x31\x74\x7c\xa5\xcc\x58\x09\xc6\x46\x13\x9e\xf1\x96\x66\xb5\x03\x3a\xaa\x03\x78\x6e\x93\xdb\x9e\x70\x74\x05\xaa\x30\xca\x00\xf3\xc3\xef\xd0\x2e\xb5\xc3\x6c\xbd\x7f\xbc\x41\xd2\x1a\x83\xec\x72\xb0\x8c\x35\x9e\xcf\x6f\x16\x90\x6b\xdb\xa5\x88\xc6\xdc\x05\x8c\xdf\x51\x99\xa7\xca\xa9\xe3\x59\x68\x20\xb2\xdd\x63\xab\xd9\x27\xec\xcb\x1a\x2c\xac\xed\x5d\x8c\x97\x04\xbe\x22\x76\xbb\x14\x3b\x5f\xff\xc3\x78\xe2\xed\x79\xea\xdf\xc4\x5a\x28\xf2\x7a\xcb\x20\x68\x7b\xdc\xbf\xf2\x77\x61\x56\x5a\xe1"),  // macro-calculated hash
+},
+{
+  std::string_view("\x23\x66\x8c\x45\x63\x79\xfb\x31\x74\x7c\xa5\xcc\x58\x09\xc6\x46\x13\x9e\xf1\x96\x66\xb5\x03\x3a\xaa\x03\x78\x6e\x93\xdb\x9e\x70\x74\x05\xaa\x30\xca\x00\xf3\xc3\xef\xd0\x2e\xb5\xc3\x6c\xbd\x7f\xbc\x41\xd2\x1a\x83\xec\x72\xb0\x8c\x35\x9e\xcf\x6f\x16\x90\x6b\xdb\xa5\x88\xc6\xdc\x05\x8c\xdf\x51\x99\xa7\xca\xa9\xe3\x59\x68\x20\xb2\xdd\x63\xab\xd9\x27\xec\xcb\x1a\x2c\xac\xed\x5d\x8c\x97\x04\xbe\x22\x76\xbb\x14\x3b\x5f\xff\xc3\x78\xe2\xed\x79\xea\xdf\xc4\x5a\x28\xf2\x7a\xcb\x20\x68\x7b\xdc\xbf\xf2\x77\x61\x56\x5a\xe1", 129u),
+  96u,  // fixed hash length
+  UINT32_C(598421397),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH("\x23\x66\x8c\x45\x63\x79\xfb\x31\x74\x7c\xa5\xcc\x58\x09\xc6\x46\x13\x9e\xf1\x96\x66\xb5\x03\x3a\xaa\x03\x78\x6e\x93\xdb\x9e\x70\x74\x05\xaa\x30\xca\x00\xf3\xc3\xef\xd0\x2e\xb5\xc3\x6c\xbd\x7f\xbc\x41\xd2\x1a\x83\xec\x72\xb0\x8c\x35\x9e\xcf\x6f\x16\x90\x6b\xdb\xa5\x88\xc6\xdc\x05\x8c\xdf\x51\x99\xa7\xca\xa9\xe3\x59\x68\x20\xb2\xdd\x63\xab\xd9\x27\xec\xcb\x1a\x2c\xac\xed\x5d\x8c\x97\x04\xbe\x22\x76\xbb\x14\x3b\x5f\xff\xc3\x78\xe2\xed\x79\xea\xdf\xc4\x5a\x28\xf2\x7a\xcb\x20\x68\x7b\xdc\xbf\xf2\x77\x61\x56\x5a\xe1"),  // macro-calculated hash
+},
+{
+  std::string_view("\x23\x66\x8c\x45\x63\x79\xfb\x31\x74\x7c\xa5\xcc\x58\x09\xc6\x46\x13\x9e\xf1\x96\x66\xb5\x03\x3a\xaa\x03\x78\x6e\x93\xdb\x9e\x70\x74\x05\xaa\x30\xca\x00\xf3\xc3\xef\xd0\x2e\xb5\xc3\x6c\xbd\x7f\xbc\x41\xd2\x1a\x83\xec\x72\xb0\x8c\x35\x9e\xcf\x6f\x16\x90\x6b\xdb\xa5\x88\xc6\xdc\x05\x8c\xdf\x51\x99\xa7\xca\xa9\xe3\x59\x68\x20\xb2\xdd\x63\xab\xd9\x27\xec\xcb\x1a\x2c\xac\xed\x5d\x8c\x97\x04\xbe\x22\x76\xbb\x14\x3b\x5f\xff\xc3\x78\xe2\xed\x79\xea\xdf\xc4\x5a\x28\xf2\x7a\xcb\x20\x68\x7b\xdc\xbf\xf2\x77\x61\x56\x5a\xe1", 129u),
+  128u,  // fixed hash length
+  UINT32_C(1313299978),  // Python-calculated hash
+  PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH("\x23\x66\x8c\x45\x63\x79\xfb\x31\x74\x7c\xa5\xcc\x58\x09\xc6\x46\x13\x9e\xf1\x96\x66\xb5\x03\x3a\xaa\x03\x78\x6e\x93\xdb\x9e\x70\x74\x05\xaa\x30\xca\x00\xf3\xc3\xef\xd0\x2e\xb5\xc3\x6c\xbd\x7f\xbc\x41\xd2\x1a\x83\xec\x72\xb0\x8c\x35\x9e\xcf\x6f\x16\x90\x6b\xdb\xa5\x88\xc6\xdc\x05\x8c\xdf\x51\x99\xa7\xca\xa9\xe3\x59\x68\x20\xb2\xdd\x63\xab\xd9\x27\xec\xcb\x1a\x2c\xac\xed\x5d\x8c\x97\x04\xbe\x22\x76\xbb\x14\x3b\x5f\xff\xc3\x78\xe2\xed\x79\xea\xdf\xc4\x5a\x28\xf2\x7a\xcb\x20\x68\x7b\xdc\xbf\xf2\x77\x61\x56\x5a\xe1"),  // macro-calculated hash
+},
+
+};  // kHashTests
+
+// clang-format on
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/pw_tokenizer_private/tokenize_test.h b/pw_tokenizer/pw_tokenizer_private/tokenize_test.h
new file mode 100644
index 0000000..7ed6f15
--- /dev/null
+++ b/pw_tokenizer/pw_tokenizer_private/tokenize_test.h
@@ -0,0 +1,46 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// Functions to test that the tokenization macro works correctly in C code.
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "pw_preprocessor/util.h"
+
+PW_EXTERN_C_START
+
+#define TEST_FORMAT_STRING_SHORT_FLOAT "Hello %s! %hd %e"
+
+void pw_TokenizeToBufferTest_StringShortFloat(void* buffer,
+                                              size_t* buffer_size);
+
+#define TEST_FORMAT_SEQUENTIAL_ZIG_ZAG "%u%d%02x%X%hu%hhd%d%ld%lu%lld%llu%c%c%c"
+
+void pw_TokenizeToBufferTest_SequentialZigZag(void* buffer,
+                                              size_t* buffer_size);
+
+void pw_TokenizeToCallbackTest_SequentialZigZag(
+    void (*callback)(const uint8_t* buffer, size_t size));
+
+void pw_TokenizeToGlobalHandlerTest_SequentialZigZag(void);
+
+void pw_TokenizeToGlobalHandlerWithPayloadTest_SequentialZigZag(void);
+
+#define TEST_FORMAT_REQUIRES_8 "Won't fit : %s%d"
+
+void pw_TokenizeToBufferTest_Requires8(void* buffer, size_t* buffer_size);
+
+PW_EXTERN_C_END
diff --git a/pw_tokenizer/pw_tokenizer_private/tokenized_string_decoding_test_data.h b/pw_tokenizer/pw_tokenizer_private/tokenized_string_decoding_test_data.h
new file mode 100644
index 0000000..03d2021
--- /dev/null
+++ b/pw_tokenizer/pw_tokenizer_private/tokenized_string_decoding_test_data.h
@@ -0,0 +1,417 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// AUTOGENERATED - DO NOT EDIT
+// This file contains test data generated by generate_decoding_test_data.cc.
+#pragma once
+
+#include <string_view>
+#include <tuple>
+
+namespace pw::test::tokenized_string_decoding {
+
+using namespace std::literals::string_view_literals;
+
+// clang-format off
+using TestCase = std::tuple<const char*, std::string_view, std::string_view>;
+
+inline constexpr TestCase kTestData[] = {
+
+// Simple strings
+TestCase("%s", "SFO", "\x03\x53\x46\x4f"sv),
+TestCase("%s", "KSJC", "\x04\x4b\x53\x4a\x43"sv),
+TestCase("%s", "", "\x00"sv),
+TestCase("%5s%s", "   nofun", "\x02\x6e\x6f\x03\x66\x75\x6e"sv),
+TestCase("%5s%s", "abcdef", "\x06\x61\x62\x63\x64\x65\x66\x00"sv),
+TestCase("%5s%s", "     abcdef", "\x00\x06\x61\x62\x63\x64\x65\x66"sv),
+TestCase("%s %-6s%s%s%s", "Intel 80586 toaster oven", "\x05\x49\x6e\x74\x65\x6c\x05\x38\x30\x35\x38\x36\x07\x74\x6f\x61\x73\x74\x65\x72\x01\x20\x04\x6f\x76\x65\x6e"sv),
+TestCase("%s %-6s%s%s%s", "Apple automatic pencil sharpener", "\x05\x41\x70\x70\x6c\x65\x09\x61\x75\x74\x6f\x6d\x61\x74\x69\x63\x07\x20\x70\x65\x6e\x63\x69\x6c\x01\x20\x09\x73\x68\x61\x72\x70\x65\x6e\x65\x72"sv),
+
+// Zero-length strings
+TestCase("%s-%s", "so-", "\x02\x73\x6f\x00"sv),
+TestCase("%s-%s", "-cool", "\x00\x04\x63\x6f\x6f\x6c"sv),
+TestCase("%s%s%3s%s", "   ", "\x00\x00\x00\x00"sv),
+TestCase("(%5s)(%2s)(%7s)", "([...])(  )(  [...])", "\x80\x00\x80"sv),
+
+// Invalid strings
+TestCase("%s", "<[%s ERROR (hi)]>", "\x03\x68\x69"sv),
+TestCase("%30s", "<[%30s ERROR (hi)]>", "\x03\x68\x69"sv),
+TestCase("%30s", "<[%30s ERROR (hi)]>", "\x83\x68\x69"sv),
+TestCase("%s", "<[%s ERROR (yo!)]>", "\x85\x79\x6f\x21"sv),
+TestCase("%s", "<[%s ERROR]>", "\x01"sv),
+TestCase("%30s", "<[%30s ERROR]>", "\x81"sv),
+
+// Continue after truncated string
+TestCase("%s %d %s", "go[...] 2 lunch", "\x82\x67\x6f\x04\x05\x6c\x75\x6e\x63\x68"sv),
+TestCase("%6s%s%s", " [...]hello[...]there", "\x80\x85\x68\x65\x6c\x6c\x6f\x05\x74\x68\x65\x72\x65"sv),
+
+// Floating point
+TestCase("%1.1f", "0.0", "\x00\x00\x00\x00"sv),
+TestCase("%0.5f", "3.14159", "\xdb\x0f\x49\x40"sv),
+
+// Character
+TestCase("%c", " ", "\x40"sv),
+TestCase("%c", "$", "\x48"sv),
+TestCase("%c", "$", "\x48"sv),
+TestCase("100%c!", "100%!", "\x4a"sv),
+
+// Atypical argument types
+TestCase("%ju", "99", "\xc6\x01"sv),
+TestCase("%jd", "99", "\xc6\x01"sv),
+TestCase("%zu", "8", "\x10"sv),
+TestCase("%zd", "123", "\xf6\x01"sv),
+TestCase("%td", "99", "\xc6\x01"sv),
+
+// Percent character
+TestCase("%%", "%", ""sv),
+TestCase("%%%%%%%%", "%%%%", "\x61\x62\x63"sv),
+TestCase("whoa%%%%wow%%%%!%%", "whoa%%wow%%!%", ""sv),
+TestCase("This is %d%% effective", "This is 1% effective", "\x02"sv),
+TestCase("%% is 100%sa%%sign%%%s", "% is 100%a%sign%OK?", "\x01\x25\x03\x4f\x4b\x3f"sv),
+
+// Percent character prints after errors
+TestCase("%s%%", "-10[...]%", "\x83\x2d\x31\x30\x00"sv),
+TestCase("%d%% is a good %%", "<[%d MISSING]>% is a good %", ""sv),
+
+// Various format strings
+TestCase("!", "!", ""sv),
+TestCase("%s", "%s", "\x02\x25\x73"sv),
+TestCase("%s", "hello", "\x05\x68\x65\x6c\x6c\x6f"sv),
+TestCase("%s%s", "Helloold", "\x05\x48\x65\x6c\x6c\x6f\x03\x6f\x6c\x64"sv),
+TestCase("%s to the%c%s", "hello to the whirled", "\x05\x68\x65\x6c\x6c\x6f\x40\x07\x77\x68\x69\x72\x6c\x65\x64"sv),
+TestCase("hello %s %d %d %d", "hello rolled 1 2 3", "\x06\x72\x6f\x6c\x6c\x65\x64\x02\x04\x06"sv),
+TestCase("", "", ""sv),
+TestCase("This has no specifiers", "This has no specifiers", ""sv),
+TestCase("%s_or_%3s", "hello_or_ hi", "\x05\x68\x65\x6c\x6c\x6f\x02\x68\x69"sv),
+TestCase("%s_or_%3d", "hello_or_-64", "\x05\x68\x65\x6c\x6c\x6f\x7f"sv),
+TestCase("%s or hi%c pi=%1.2e", "hello or hi! pi=3.14e+00", "\x05\x68\x65\x6c\x6c\x6f\x42\xdb\x0f\x49\x40"sv),
+TestCase("Why, %s there. My favorite number is %.2f%c", "Why, hello there. My favorite number is 3.14!", "\x05\x68\x65\x6c\x6c\x6f\xdb\x0f\x49\x40\x42"sv),
+
+// Various errors
+TestCase("%d", "<[%d MISSING]>", ""sv),
+TestCase("ABC%d123%dabc%dABC", "ABC<[%d MISSING]>123<[%d SKIPPED]>abc<[%d SKIPPED]>ABC", ""sv),
+TestCase("%sXY%+ldxy%a", "Yo![...]XY<[%+ld ERROR]>xy<[%a SKIPPED]>", "\x83\x59\x6f\x21\x80"sv),
+TestCase("%d", "<[%d MISSING]>", ""sv),
+TestCase("%sXY%+ldxy%a", "Yo![...]XY<[%+ld ERROR]>xy<[%a SKIPPED]>", "\x83\x59\x6f\x21\x80"sv),
+TestCase("%s%lld%9u", "$[...]<[%lld ERROR]><[%9u SKIPPED]>", "\x81\x24\x80\x80"sv),
+
+// Alternate form (#)
+TestCase("Hex: %#x", "Hex: 0xbeef", "\xde\xfb\x05"sv),
+TestCase("Hex: %#08X", "Hex: 0X00FEED", "\xda\xfb\x07"sv),
+
+// Random integers
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.210e-013E-01000.3210430.3210430.3210430.321043", "\xcb\x5f\xa4\x3e\xcb\x5f\xa4\x3e\x00\xcb\x5f\xa4\x3e\xcb\x5f\xa4\x3e\xcb\x5f\xa4\x3e\xcb\x5f\xa4\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.914e-012E-01010.191430.191430.1914300.191430", "\x4c\x06\x44\x3e\x4c\x06\x44\x3e\x02\x4c\x06\x44\x3e\x4c\x06\x44\x3e\x4c\x06\x44\x3e\x4c\x06\x44\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.087e-011E-01020.108680.108680.1086800.108680", "\xb5\x93\xde\x3d\xb5\x93\xde\x3d\x04\xb5\x93\xde\x3d\xb5\x93\xde\x3d\xb5\x93\xde\x3d\xb5\x93\xde\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.821e-012E-01030.1821430.1821430.1821430.182143", "\xd3\x83\x3a\x3e\xd3\x83\x3a\x3e\x06\xd3\x83\x3a\x3e\xd3\x83\x3a\x3e\xd3\x83\x3a\x3e\xd3\x83\x3a\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +4.159e-014E-01040.4159270.4159270.4159270.415927", "\x56\xf4\xd4\x3e\x56\xf4\xd4\x3e\x08\x56\xf4\xd4\x3e\x56\xf4\xd4\x3e\x56\xf4\xd4\x3e\x56\xf4\xd4\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.019e-019E-01050.9018610.9018610.9018610.901861", "\x64\xe0\x66\x3f\x64\xe0\x66\x3f\x0a\x64\xe0\x66\x3f\x64\xe0\x66\x3f\x64\xe0\x66\x3f\x64\xe0\x66\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.429e-013E-01060.3429150.3429150.3429150.342915", "\x8c\x92\xaf\x3e\x8c\x92\xaf\x3e\x0c\x8c\x92\xaf\x3e\x8c\x92\xaf\x3e\x8c\x92\xaf\x3e\x8c\x92\xaf\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.011e-017E-01070.7011480.7011480.7011480.701148", "\x68\x7e\x33\x3f\x68\x7e\x33\x3f\x0e\x68\x7e\x33\x3f\x68\x7e\x33\x3f\x68\x7e\x33\x3f\x68\x7e\x33\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.988e-017E-01080.6988460.6988460.6988460.698846", "\x96\xe7\x32\x3f\x96\xe7\x32\x3f\x10\x96\xe7\x32\x3f\x96\xe7\x32\x3f\x96\xe7\x32\x3f\x96\xe7\x32\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.383e-018E-01090.8383340.8383340.8383340.838334", "\x0e\x9d\x56\x3f\x0e\x9d\x56\x3f\x12\x0e\x9d\x56\x3f\x0e\x9d\x56\x3f\x0e\x9d\x56\x3f\x0e\x9d\x56\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.469e-012E-01100.2468540.2468540.2468540.246854", "\x6b\xc7\x7c\x3e\x6b\xc7\x7c\x3e\x14\x6b\xc7\x7c\x3e\x6b\xc7\x7c\x3e\x6b\xc7\x7c\x3e\x6b\xc7\x7c\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.634e-011E+00110.9634290.9634290.9634290.963429", "\x4a\xa3\x76\x3f\x4a\xa3\x76\x3f\x16\x4a\xa3\x76\x3f\x4a\xa3\x76\x3f\x4a\xa3\x76\x3f\x4a\xa3\x76\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.454e-013E-01120.3454440.3454440.3454440.345444", "\xf9\xdd\xb0\x3e\xf9\xdd\xb0\x3e\x18\xf9\xdd\xb0\x3e\xf9\xdd\xb0\x3e\xf9\xdd\xb0\x3e\xf9\xdd\xb0\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.344e-019E-01130.9344260.9344260.9344260.934426", "\x83\x36\x6f\x3f\x83\x36\x6f\x3f\x1a\x83\x36\x6f\x3f\x83\x36\x6f\x3f\x83\x36\x6f\x3f\x83\x36\x6f\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.880e-014E-01140.3879820.3879820.3879820.387982", "\x88\xa5\xc6\x3e\x88\xa5\xc6\x3e\x1c\x88\xa5\xc6\x3e\x88\xa5\xc6\x3e\x88\xa5\xc6\x3e\x88\xa5\xc6\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.852e-012E-01150.1851670.1851670.1851670.185167", "\x7c\x9c\x3d\x3e\x7c\x9c\x3d\x3e\x1e\x7c\x9c\x3d\x3e\x7c\x9c\x3d\x3e\x7c\x9c\x3d\x3e\x7c\x9c\x3d\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.251e-015E-01160.5250540.5250540.5250540.525054", "\xe9\x69\x06\x3f\xe9\x69\x06\x3f\x20\xe9\x69\x06\x3f\xe9\x69\x06\x3f\xe9\x69\x06\x3f\xe9\x69\x06\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.463e-027E-02170.07463110.07463110.0746310.074631", "\x2a\xd8\x98\x3d\x2a\xd8\x98\x3d\x22\x2a\xd8\x98\x3d\x2a\xd8\x98\x3d\x2a\xd8\x98\x3d\x2a\xd8\x98\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.482e-018E-01180.8481530.8481530.8481530.848153", "\x88\x20\x59\x3f\x88\x20\x59\x3f\x24\x88\x20\x59\x3f\x88\x20\x59\x3f\x88\x20\x59\x3f\x88\x20\x59\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.654e-012E-01190.1654280.1654280.1654280.165428", "\xda\x65\x29\x3e\xda\x65\x29\x3e\x26\xda\x65\x29\x3e\xda\x65\x29\x3e\xda\x65\x29\x3e\xda\x65\x29\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.280e-016E-01200.627980.627980.6279800.627980", "\x47\xc3\x20\x3f\x47\xc3\x20\x3f\x28\x47\xc3\x20\x3f\x47\xc3\x20\x3f\x47\xc3\x20\x3f\x47\xc3\x20\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.176e-016E-01210.6175810.6175810.6175810.617581", "\xd2\x19\x1e\x3f\xd2\x19\x1e\x3f\x2a\xd2\x19\x1e\x3f\xd2\x19\x1e\x3f\xd2\x19\x1e\x3f\xd2\x19\x1e\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.034e-015E-01220.5033950.5033950.5033950.503395", "\x85\xde\x00\x3f\x85\xde\x00\x3f\x2c\x85\xde\x00\x3f\x85\xde\x00\x3f\x85\xde\x00\x3f\x85\xde\x00\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.409e-013E-01230.340910.340910.3409100.340910", "\xcb\x8b\xae\x3e\xcb\x8b\xae\x3e\x2e\xcb\x8b\xae\x3e\xcb\x8b\xae\x3e\xcb\x8b\xae\x3e\xcb\x8b\xae\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.797e-026E-02240.05796970.05796970.0579700.057970", "\x9d\x71\x6d\x3d\x9d\x71\x6d\x3d\x30\x9d\x71\x6d\x3d\x9d\x71\x6d\x3d\x9d\x71\x6d\x3d\x9d\x71\x6d\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.424e-019E-01250.9423620.9423620.9423620.942362", "\x9e\x3e\x71\x3f\x9e\x3e\x71\x3f\x32\x9e\x3e\x71\x3f\x9e\x3e\x71\x3f\x9e\x3e\x71\x3f\x9e\x3e\x71\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.129e-016E-01260.6128640.6128640.6128640.612864", "\xa9\xe4\x1c\x3f\xa9\xe4\x1c\x3f\x34\xa9\xe4\x1c\x3f\xa9\xe4\x1c\x3f\xa9\xe4\x1c\x3f\xa9\xe4\x1c\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.093e-013E-01270.3093140.3093140.3093140.309314", "\x5e\x5e\x9e\x3e\x5e\x5e\x9e\x3e\x36\x5e\x5e\x9e\x3e\x5e\x5e\x9e\x3e\x5e\x5e\x9e\x3e\x5e\x5e\x9e\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.864e-018E-01280.7863820.7863820.7863820.786382", "\x4e\x50\x49\x3f\x4e\x50\x49\x3f\x38\x4e\x50\x49\x3f\x4e\x50\x49\x3f\x4e\x50\x49\x3f\x4e\x50\x49\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.105e-015E-01290.5104890.5104890.5104890.510489", "\x65\xaf\x02\x3f\x65\xaf\x02\x3f\x3a\x65\xaf\x02\x3f\x65\xaf\x02\x3f\x65\xaf\x02\x3f\x65\xaf\x02\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.358e-011E-01300.1357620.1357620.1357620.135762", "\x16\x05\x0b\x3e\x16\x05\x0b\x3e\x3c\x16\x05\x0b\x3e\x16\x05\x0b\x3e\x16\x05\x0b\x3e\x16\x05\x0b\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.879e-011E+00310.987870.987870.9878700.987870", "\x14\xe5\x7c\x3f\x14\xe5\x7c\x3f\x3e\x14\xe5\x7c\x3f\x14\xe5\x7c\x3f\x14\xe5\x7c\x3f\x14\xe5\x7c\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.994e-012E-01320.1994450.1994450.1994450.199445", "\x48\x3b\x4c\x3e\x48\x3b\x4c\x3e\x40\x48\x3b\x4c\x3e\x48\x3b\x4c\x3e\x48\x3b\x4c\x3e\x48\x3b\x4c\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.042e-025E-02330.0504220.0504220.0504220.050422", "\x47\x87\x4e\x3d\x47\x87\x4e\x3d\x42\x47\x87\x4e\x3d\x47\x87\x4e\x3d\x47\x87\x4e\x3d\x47\x87\x4e\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.875e-028E-02340.07875130.07875130.0787510.078751", "\x59\x48\xa1\x3d\x59\x48\xa1\x3d\x44\x59\x48\xa1\x3d\x59\x48\xa1\x3d\x59\x48\xa1\x3d\x59\x48\xa1\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.121e-019E-01350.9120510.9120510.9120510.912051", "\x2b\x7c\x69\x3f\x2b\x7c\x69\x3f\x46\x2b\x7c\x69\x3f\x2b\x7c\x69\x3f\x2b\x7c\x69\x3f\x2b\x7c\x69\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.001e-011E-01360.1000970.1000970.1000970.100097", "\xaf\xff\xcc\x3d\xaf\xff\xcc\x3d\x48\xaf\xff\xcc\x3d\xaf\xff\xcc\x3d\xaf\xff\xcc\x3d\xaf\xff\xcc\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.278e-015E-01370.5277510.5277510.5277510.527751", "\xaa\x1a\x07\x3f\xaa\x1a\x07\x3f\x4a\xaa\x1a\x07\x3f\xaa\x1a\x07\x3f\xaa\x1a\x07\x3f\xaa\x1a\x07\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.040e-017E-01380.7039560.7039560.7039560.703956", "\x76\x36\x34\x3f\x76\x36\x34\x3f\x4c\x76\x36\x34\x3f\x76\x36\x34\x3f\x76\x36\x34\x3f\x76\x36\x34\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.794e-019E-01390.8793580.8793580.8793580.879358", "\x96\x1d\x61\x3f\x96\x1d\x61\x3f\x4e\x96\x1d\x61\x3f\x96\x1d\x61\x3f\x96\x1d\x61\x3f\x96\x1d\x61\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.455e-012E-01400.2454720.2454720.2454720.245472", "\x1c\x5d\x7b\x3e\x1c\x5d\x7b\x3e\x50\x1c\x5d\x7b\x3e\x1c\x5d\x7b\x3e\x1c\x5d\x7b\x3e\x1c\x5d\x7b\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.607e-017E-01410.6606620.6606620.6606620.660662", "\x1f\x21\x29\x3f\x1f\x21\x29\x3f\x52\x1f\x21\x29\x3f\x1f\x21\x29\x3f\x1f\x21\x29\x3f\x1f\x21\x29\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +4.550e-015E-01420.4550340.4550340.4550340.455034", "\x46\xfa\xe8\x3e\x46\xfa\xe8\x3e\x54\x46\xfa\xe8\x3e\x46\xfa\xe8\x3e\x46\xfa\xe8\x3e\x46\xfa\xe8\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.344e-015E-01430.5343630.5343630.5343630.534363", "\x0b\xcc\x08\x3f\x0b\xcc\x08\x3f\x56\x0b\xcc\x08\x3f\x0b\xcc\x08\x3f\x0b\xcc\x08\x3f\x0b\xcc\x08\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.146e-012E-01440.2145620.2145620.2145620.214562", "\x09\xb6\x5b\x3e\x09\xb6\x5b\x3e\x58\x09\xb6\x5b\x3e\x09\xb6\x5b\x3e\x09\xb6\x5b\x3e\x09\xb6\x5b\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.157e-015E-01450.5156980.5156980.5156980.515698", "\xc3\x04\x04\x3f\xc3\x04\x04\x3f\x5a\xc3\x04\x04\x3f\xc3\x04\x04\x3f\xc3\x04\x04\x3f\xc3\x04\x04\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.148e-017E-01460.7147680.7147680.7147680.714768", "\x0b\xfb\x36\x3f\x0b\xfb\x36\x3f\x5c\x0b\xfb\x36\x3f\x0b\xfb\x36\x3f\x0b\xfb\x36\x3f\x0b\xfb\x36\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.730e-012E-01470.172960.172960.1729600.172960", "\x62\x1c\x31\x3e\x62\x1c\x31\x3e\x5e\x62\x1c\x31\x3e\x62\x1c\x31\x3e\x62\x1c\x31\x3e\x62\x1c\x31\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.971e-017E-01480.6970990.6970990.6970990.697099", "\x1b\x75\x32\x3f\x1b\x75\x32\x3f\x60\x1b\x75\x32\x3f\x1b\x75\x32\x3f\x1b\x75\x32\x3f\x1b\x75\x32\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.768e-013E-01490.2767580.2767580.2767580.276758", "\x31\xb3\x8d\x3e\x31\xb3\x8d\x3e\x62\x31\xb3\x8d\x3e\x31\xb3\x8d\x3e\x31\xb3\x8d\x3e\x31\xb3\x8d\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.734e-012E-01500.1734190.1734190.1734190.173419", "\xa2\x94\x31\x3e\xa2\x94\x31\x3e\x64\xa2\x94\x31\x3e\xa2\x94\x31\x3e\xa2\x94\x31\x3e\xa2\x94\x31\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.692e-011E+00510.9692260.9692260.9692260.969226", "\x32\x1f\x78\x3f\x32\x1f\x78\x3f\x66\x32\x1f\x78\x3f\x32\x1f\x78\x3f\x32\x1f\x78\x3f\x32\x1f\x78\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.781e-019E-01520.8781310.8781310.8781310.878131", "\x35\xcd\x60\x3f\x35\xcd\x60\x3f\x68\x35\xcd\x60\x3f\x35\xcd\x60\x3f\x35\xcd\x60\x3f\x35\xcd\x60\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.279e-013E-01530.3279260.3279260.3279260.327926", "\xf6\xe5\xa7\x3e\xf6\xe5\xa7\x3e\x6a\xf6\xe5\xa7\x3e\xf6\xe5\xa7\x3e\xf6\xe5\xa7\x3e\xf6\xe5\xa7\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.008e-017E-01540.7007780.7007780.7007780.700778", "\x31\x66\x33\x3f\x31\x66\x33\x3f\x6c\x31\x66\x33\x3f\x31\x66\x33\x3f\x31\x66\x33\x3f\x31\x66\x33\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.957e-011E+00550.9956590.9956590.9956590.995659", "\x84\xe3\x7e\x3f\x84\xe3\x7e\x3f\x6e\x84\xe3\x7e\x3f\x84\xe3\x7e\x3f\x84\xe3\x7e\x3f\x84\xe3\x7e\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.110e-012E-01560.2110.2110.2110000.211000", "\x6a\x10\x58\x3e\x6a\x10\x58\x3e\x70\x6a\x10\x58\x3e\x6a\x10\x58\x3e\x6a\x10\x58\x3e\x6a\x10\x58\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.794e-016E-01570.579380.579380.5793800.579380", "\x3a\x52\x14\x3f\x3a\x52\x14\x3f\x72\x3a\x52\x14\x3f\x3a\x52\x14\x3f\x3a\x52\x14\x3f\x3a\x52\x14\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.704e-014E-01580.3703670.3703670.3703670.370367", "\xb4\xa0\xbd\x3e\xb4\xa0\xbd\x3e\x74\xb4\xa0\xbd\x3e\xb4\xa0\xbd\x3e\xb4\xa0\xbd\x3e\xb4\xa0\xbd\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +1.978e-012E-01590.1977640.1977640.1977640.197764", "\x95\x82\x4a\x3e\x95\x82\x4a\x3e\x76\x95\x82\x4a\x3e\x95\x82\x4a\x3e\x95\x82\x4a\x3e\x95\x82\x4a\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.440e-018E-01600.8439990.8439990.8439990.843999", "\x4d\x10\x58\x3f\x4d\x10\x58\x3f\x78\x4d\x10\x58\x3f\x4d\x10\x58\x3f\x4d\x10\x58\x3f\x4d\x10\x58\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.444e-019E-01610.9443610.9443610.9443610.944361", "\xa9\xc1\x71\x3f\xa9\xc1\x71\x3f\x7a\xa9\xc1\x71\x3f\xa9\xc1\x71\x3f\xa9\xc1\x71\x3f\xa9\xc1\x71\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +4.393e-024E-02620.04392670.04392670.0439270.043927", "\x72\xec\x33\x3d\x72\xec\x33\x3d\x7c\x72\xec\x33\x3d\x72\xec\x33\x3d\x72\xec\x33\x3d\x72\xec\x33\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.167e-018E-01630.8167210.8167210.8167210.816721", "\xa2\x14\x51\x3f\xa2\x14\x51\x3f\x7e\xa2\x14\x51\x3f\xa2\x14\x51\x3f\xa2\x14\x51\x3f\xa2\x14\x51\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.951e-019E-01640.8951460.8951460.8951460.895146", "\x43\x28\x65\x3f\x43\x28\x65\x3f\x80\x01\x43\x28\x65\x3f\x43\x28\x65\x3f\x43\x28\x65\x3f\x43\x28\x65\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.260e-025E-02650.05259920.05259920.0525990.052599", "\x38\x72\x57\x3d\x38\x72\x57\x3d\x82\x01\x38\x72\x57\x3d\x38\x72\x57\x3d\x38\x72\x57\x3d\x38\x72\x57\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.882e-019E-01660.8882180.8882180.8882180.888218", "\x3f\x62\x63\x3f\x3f\x62\x63\x3f\x84\x01\x3f\x62\x63\x3f\x3f\x62\x63\x3f\x3f\x62\x63\x3f\x3f\x62\x63\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +4.788e-015E-01670.4787930.4787930.4787930.478793", "\x6b\x24\xf5\x3e\x6b\x24\xf5\x3e\x86\x01\x6b\x24\xf5\x3e\x6b\x24\xf5\x3e\x6b\x24\xf5\x3e\x6b\x24\xf5\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.328e-026E-02680.06327770.06327770.0632780.063278", "\xbb\x97\x81\x3d\xbb\x97\x81\x3d\x88\x01\xbb\x97\x81\x3d\xbb\x97\x81\x3d\xbb\x97\x81\x3d\xbb\x97\x81\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.968e-019E-01690.896790.896790.8967900.896790", "\x07\x94\x65\x3f\x07\x94\x65\x3f\x8a\x01\x07\x94\x65\x3f\x07\x94\x65\x3f\x07\x94\x65\x3f\x07\x94\x65\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.153e-013E-01700.3153250.3153250.3153250.315325", "\x4c\x72\xa1\x3e\x4c\x72\xa1\x3e\x8c\x01\x4c\x72\xa1\x3e\x4c\x72\xa1\x3e\x4c\x72\xa1\x3e\x4c\x72\xa1\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.968e-019E-01710.8968210.8968210.8968210.896821", "\x0b\x96\x65\x3f\x0b\x96\x65\x3f\x8e\x01\x0b\x96\x65\x3f\x0b\x96\x65\x3f\x0b\x96\x65\x3f\x0b\x96\x65\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.917e-019E-01720.8916720.8916720.8916720.891672", "\x98\x44\x64\x3f\x98\x44\x64\x3f\x90\x01\x98\x44\x64\x3f\x98\x44\x64\x3f\x98\x44\x64\x3f\x98\x44\x64\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.630e-013E-01730.2629920.2629920.2629920.262992", "\xe0\xa6\x86\x3e\xe0\xa6\x86\x3e\x92\x01\xe0\xa6\x86\x3e\xe0\xa6\x86\x3e\xe0\xa6\x86\x3e\xe0\xa6\x86\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.461e-017E-01740.7460720.7460720.7460720.746072", "\x95\xfe\x3e\x3f\x95\xfe\x3e\x3f\x94\x01\x95\xfe\x3e\x3f\x95\xfe\x3e\x3f\x95\xfe\x3e\x3f\x95\xfe\x3e\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.495e-018E-01750.8495190.8495190.8495190.849519", "\x0f\x7a\x59\x3f\x0f\x7a\x59\x3f\x96\x01\x0f\x7a\x59\x3f\x0f\x7a\x59\x3f\x0f\x7a\x59\x3f\x0f\x7a\x59\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +4.244e-014E-01760.4244340.4244340.4244340.424434", "\x63\x4f\xd9\x3e\x63\x4f\xd9\x3e\x98\x01\x63\x4f\xd9\x3e\x63\x4f\xd9\x3e\x63\x4f\xd9\x3e\x63\x4f\xd9\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.790e-011E+00770.9789560.9789560.9789560.978956", "\xe2\x9c\x7a\x3f\xe2\x9c\x7a\x3f\x9a\x01\xe2\x9c\x7a\x3f\xe2\x9c\x7a\x3f\xe2\x9c\x7a\x3f\xe2\x9c\x7a\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.633e-016E-01780.5632670.5632670.5632670.563267", "\x48\x32\x10\x3f\x48\x32\x10\x3f\x9c\x01\x48\x32\x10\x3f\x48\x32\x10\x3f\x48\x32\x10\x3f\x48\x32\x10\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.106e-012E-01790.2106230.2106230.2106230.210623", "\x7d\xad\x57\x3e\x7d\xad\x57\x3e\x9e\x01\x7d\xad\x57\x3e\x7d\xad\x57\x3e\x7d\xad\x57\x3e\x7d\xad\x57\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.172e-013E-01800.3171850.3171850.3171850.317185", "\x22\x66\xa2\x3e\x22\x66\xa2\x3e\xa0\x01\x22\x66\xa2\x3e\x22\x66\xa2\x3e\x22\x66\xa2\x3e\x22\x66\xa2\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.624e-017E-01810.6623550.6623550.6623550.662355", "\x18\x90\x29\x3f\x18\x90\x29\x3f\xa2\x01\x18\x90\x29\x3f\x18\x90\x29\x3f\x18\x90\x29\x3f\x18\x90\x29\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.895e-014E-01820.3895240.3895240.3895240.389524", "\xba\x6f\xc7\x3e\xba\x6f\xc7\x3e\xa4\x01\xba\x6f\xc7\x3e\xba\x6f\xc7\x3e\xba\x6f\xc7\x3e\xba\x6f\xc7\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +9.884e-011E+00830.9883550.9883550.9883550.988355", "\xcd\x04\x7d\x3f\xcd\x04\x7d\x3f\xa6\x01\xcd\x04\x7d\x3f\xcd\x04\x7d\x3f\xcd\x04\x7d\x3f\xcd\x04\x7d\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.176e-012E-01840.2175680.2175680.2175680.217568", "\x0b\xca\x5e\x3e\x0b\xca\x5e\x3e\xa8\x01\x0b\xca\x5e\x3e\x0b\xca\x5e\x3e\x0b\xca\x5e\x3e\x0b\xca\x5e\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +4.721e-015E-01850.4721430.4721430.4721430.472143", "\xbb\xbc\xf1\x3e\xbb\xbc\xf1\x3e\xaa\x01\xbb\xbc\xf1\x3e\xbb\xbc\xf1\x3e\xbb\xbc\xf1\x3e\xbb\xbc\xf1\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.868e-019E-01860.8868160.8868160.8868160.886816", "\x62\x06\x63\x3f\x62\x06\x63\x3f\xac\x01\x62\x06\x63\x3f\x62\x06\x63\x3f\x62\x06\x63\x3f\x62\x06\x63\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.723e-019E-01870.8722940.8722940.8722940.872294", "\xa9\x4e\x5f\x3f\xa9\x4e\x5f\x3f\xae\x01\xa9\x4e\x5f\x3f\xa9\x4e\x5f\x3f\xa9\x4e\x5f\x3f\xa9\x4e\x5f\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.230e-025E-02880.05230090.05230090.0523010.052301", "\x6f\x39\x56\x3d\x6f\x39\x56\x3d\xb0\x01\x6f\x39\x56\x3d\x6f\x39\x56\x3d\x6f\x39\x56\x3d\x6f\x39\x56\x3d"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.598e-017E-01890.6598280.6598280.6598280.659828", "\x7f\xea\x28\x3f\x7f\xea\x28\x3f\xb2\x01\x7f\xea\x28\x3f\x7f\xea\x28\x3f\x7f\xea\x28\x3f\x7f\xea\x28\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.618e-016E-01900.5617660.5617660.5617660.561766", "\xe3\xcf\x0f\x3f\xe3\xcf\x0f\x3f\xb4\x01\xe3\xcf\x0f\x3f\xe3\xcf\x0f\x3f\xe3\xcf\x0f\x3f\xe3\xcf\x0f\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.281e-012E-01910.2280650.2280650.2280650.228065", "\xd6\x89\x69\x3e\xd6\x89\x69\x3e\xb6\x01\xd6\x89\x69\x3e\xd6\x89\x69\x3e\xd6\x89\x69\x3e\xd6\x89\x69\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.881e-013E-01920.2880970.2880970.2880970.288097", "\x69\x81\x93\x3e\x69\x81\x93\x3e\xb8\x01\x69\x81\x93\x3e\x69\x81\x93\x3e\x69\x81\x93\x3e\x69\x81\x93\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +7.210e-017E-01930.7209650.7209650.7209650.720965", "\x31\x91\x38\x3f\x31\x91\x38\x3f\xba\x01\x31\x91\x38\x3f\x31\x91\x38\x3f\x31\x91\x38\x3f\x31\x91\x38\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +2.152e-012E-01940.2152470.2152470.2152470.215247", "\xb3\x69\x5c\x3e\xb3\x69\x5c\x3e\xbc\x01\xb3\x69\x5c\x3e\xb3\x69\x5c\x3e\xb3\x69\x5c\x3e\xb3\x69\x5c\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +5.282e-015E-01950.5281530.5281530.5281530.528153", "\x0a\x35\x07\x3f\x0a\x35\x07\x3f\xbe\x01\x0a\x35\x07\x3f\x0a\x35\x07\x3f\x0a\x35\x07\x3f\x0a\x35\x07\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +6.896e-017E-01960.689570.689570.6895700.689570", "\xa6\x87\x30\x3f\xa6\x87\x30\x3f\xc0\x01\xa6\x87\x30\x3f\xa6\x87\x30\x3f\xa6\x87\x30\x3f\xa6\x87\x30\x3f"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.952e-014E-01970.3951650.3951650.3951650.395165", "\x0e\x53\xca\x3e\x0e\x53\xca\x3e\xc2\x01\x0e\x53\xca\x3e\x0e\x53\xca\x3e\x0e\x53\xca\x3e\x0e\x53\xca\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +3.759e-014E-01980.3759280.3759280.3759280.375928", "\x96\x79\xc0\x3e\x96\x79\xc0\x3e\xc4\x01\x96\x79\xc0\x3e\x96\x79\xc0\x3e\x96\x79\xc0\x3e\x96\x79\xc0\x3e"sv),
+TestCase("This is a number: %+08.3e%1.0E%02d%g%G%f%-3f", "This is a number: +8.436e-028E-02990.08436450.08436450.0843640.084364", "\x48\xc7\xac\x3d\x48\xc7\xac\x3d\xc6\x01\x48\xc7\xac\x3d\x48\xc7\xac\x3d\x48\xc7\xac\x3d\x48\xc7\xac\x3d"sv),
+TestCase("%s: %llu %d %c", "0: 646757550612212450 128431401 P", "\x01\x30\xc4\xab\xd7\xc0\x99\xa4\xdf\xf9\x11\xd2\xd4\xbd\x7a\xa0\x01"sv),
+TestCase("%s: %llu %d %c", "1: 5294527771240555016 1496480504 	", "\x01\x31\x90\x88\xdf\xa3\x9a\xbd\xf8\xf9\x92\x01\xf0\xeb\x93\x93\x0b\x12"sv),
+TestCase("%s: %llu %d %c", "2: 4061389961487213535 1555570883 >", "\x01\x32\xbe\x8f\xc3\xcc\xdb\xa9\xfa\xdc\x70\x86\x83\xc1\xcb\x0b\x7c"sv),
+TestCase("%s: %llu %d %c", "3: 7083829078452288754 1162540900 d", "\x01\x33\xe4\xf3\xd9\xeb\xfb\xc0\xe8\xce\xc4\x01\xc8\xdd\xd7\xd4\x08\xc8\x01"sv),
+TestCase("%s: %llu %d %c", "4: 4002626457632111277 1471392205 <", "\x01\x34\xda\xea\xef\xfb\xcd\xe3\x97\x8c\x6f\x9a\xa7\x9d\xfb\x0a\x78"sv),
+TestCase("%s: %llu %d %c", "5: 3705575452837642392 999262596 K", "\x01\x35\xb0\x82\xdc\xac\xb9\xcf\xec\xec\x66\x88\xa6\xfc\xb8\x07\x96\x01"sv),
+TestCase("%s: %llu %d %c", "6: 6557245727662973594 896373144 4", "\x01\x36\xb4\x8a\xe5\xe4\xeb\x8f\x82\x80\xb6\x01\xb0\xc6\xec\xd6\x06\x68"sv),
+TestCase("%s: %llu %d %c", "7: 5454275499512896944 1226045852 z", "\x01\x37\xe0\xf6\xa8\xf9\xe6\xaa\xbd\xb1\x97\x01\xb8\xe6\x9f\x91\x09\xf4\x01"sv),
+TestCase("%s: %llu %d %c", "8: 9044083069173435164 1766891050 '", "\x01\x38\xb8\xac\xa8\x93\xc8\xe8\x84\x83\xfb\x01\xd4\xf8\x84\x95\x0d\x4e"sv),
+TestCase("%s: %llu %d %c", "9: 5076270483973408909 977144804 ^", "\x01\x39\x9a\xa2\xc8\xd0\x84\xc7\xc4\xf2\x8c\x01\xc8\xaf\xf0\xa3\x07\xbc\x01"sv),
+TestCase("%s: %llu %d %c", "10: 6514086955327654755 2144124549 D", "\x02\x31\x30\xc6\xad\xfc\x84\xd4\xe4\xd7\xe6\xb4\x01\x8a\xfa\xe5\xfc\x0f\x88\x01"sv),
+TestCase("%s: %llu %d %c", "11: 4628058100229254151 1281962171  ", "\x02\x31\x31\x8e\xb0\xc5\x80\xcb\x94\x95\xba\x80\x01\xf6\xc2\xc9\xc6\x09\x40"sv),
+TestCase("%s: %llu %d %c", "12: 1076793340331741575 2057242876 Q", "\x02\x31\x32\x8e\xc6\xd6\xaf\xd0\xf5\xc4\xf1\x1d\xf8\xa3\xf8\xa9\x0f\xa2\x01"sv),
+TestCase("%s: %llu %d %c", "13: 5264779051526567427 890694850 .", "\x02\x31\x33\x86\xd8\xc5\xb2\xe8\xa9\xa0\x90\x92\x01\x84\xb3\xb7\xd1\x06\x5c"sv),
+TestCase("%s: %llu %d %c", "14: 6197432947465793532 1477386409 L", "\x02\x31\x34\xf8\xbf\x81\x90\xc5\x9b\xda\x81\xac\x01\xd2\x82\xf9\x80\x0b\x98\x01"sv),
+TestCase("%s: %llu %d %c", "15: 1939578830432974807 487012735 A", "\x02\x31\x35\xae\xdf\x99\x9d\xf0\xaa\xe2\xea\x35\xfe\xe5\xb9\xd0\x03\x82\x01"sv),
+TestCase("%s: %llu %d %c", "16: 4252410814844357301 2016407074 <", "\x02\x31\x36\xea\xaa\x9e\x9d\xe3\xc6\xcc\x83\x76\xc4\xb8\xff\x82\x0f\x78"sv),
+TestCase("%s: %llu %d %c", "17: 4095317955084962012 897441415 F", "\x02\x31\x37\xb8\xa3\xf2\xb1\xee\xfe\xbe\xd5\x71\x8e\xfa\xee\xd7\x06\x8c\x01"sv),
+TestCase("%s: %llu %d %c", "18: 8433702361086030159 2131660974 ~", "\x02\x31\x38\x9e\xc5\xbf\xde\xa3\xe7\xc3\x8a\xea\x01\xdc\xc2\xf4\xf0\x0f\xfc\x01"sv),
+TestCase("%s: %llu %d %c", "19: 5127196966915280688 1045937034 e", "\x02\x31\x39\xe0\xbc\x90\xa7\xbe\x9e\xbb\xa7\x8e\x01\x94\xee\xbd\xe5\x07\xca\x01"sv),
+TestCase("%s: %llu %d %c", "20: 8259453203008866922 1179139938 A", "\x02\x32\x30\xd4\xa9\xf2\xc4\xaf\xbb\xbc\x9f\xe5\x01\xc4\xfd\xc1\xe4\x08\x82\x01"sv),
+TestCase("%s: %llu %d %c", "21: 8275937406817640763 443381940 G", "\x02\x32\x31\xf6\x84\xdd\xcb\xa8\xce\x84\xda\xe5\x01\xe8\xe2\xeb\xa6\x03\x8e\x01"sv),
+TestCase("%s: %llu %d %c", "22: 997754361730893686 1007730161 e", "\x02\x32\x32\xec\x9d\x88\x81\xc8\x93\xde\xd8\x1b\xe2\xf7\x85\xc1\x07\xca\x01"sv),
+TestCase("%s: %llu %d %c", "23: 256781249510239018 1812585134 !", "\x02\x32\x33\xd4\xac\xcf\xa0\xf3\xcb\xa2\x90\x07\xdc\xea\xce\xc0\x0d\x42"sv),
+TestCase("%s: %llu %d %c", "24: 2332871340892345868 1139696535 2", "\x02\x32\x34\x98\xd8\x9e\xc9\xfb\x87\x83\xe0\x40\xae\x8e\xf3\xbe\x08\x64"sv),
+TestCase("%s: %llu %d %c", "25: 1987972021100915124 725058015 q", "\x02\x32\x35\xe8\xa6\x95\xd2\xa7\x81\xd9\x96\x37\xbe\x87\xbc\xb3\x05\xe2\x01"sv),
+TestCase("%s: %llu %d %c", "26: 5784824274936856659 746840193 y", "\x02\x32\x36\xa6\x81\xd8\xba\xeb\xc1\xe9\xc7\xa0\x01\x82\x82\x9f\xc8\x05\xf2\x01"sv),
+TestCase("%s: %llu %d %c", "27: 1164916386415315063 68230794 $", "\x02\x32\x37\xee\xa1\x88\xd5\x81\xd2\xce\xaa\x20\x94\xfa\x88\x41\x48"sv),
+TestCase("%s: %llu %d %c", "28: 3140932642079254647 430103045 r", "\x02\x32\x38\xee\xa1\x81\xa9\x97\xe6\xea\x96\x57\x8a\xe8\x96\x9a\x03\xe4\x01"sv),
+TestCase("%s: %llu %d %c", "29: 7940449326902609449 1006905623 N", "\x02\x32\x39\xd2\x98\xa8\xf0\xf5\xa5\x92\xb2\xdc\x01\xae\xa4\xa1\xc0\x07\x9c\x01"sv),
+TestCase("%s: %llu %d %c", "30: 5240496025593009868 112523603 (", "\x02\x33\x30\x98\xdb\x8a\xc5\xd6\xd7\xfd\xb9\x91\x01\xa6\xe5\xa7\x6b\x50"sv),
+TestCase("%s: %llu %d %c", "31: 5038839083535780307 849780316 ~", "\x02\x33\x31\xa6\xa7\x8a\xe8\xe2\xdc\xc6\xed\x8b\x01\xb8\xf9\xb4\xaa\x06\xfc\x01"sv),
+TestCase("%s: %llu %d %c", "32: 3630797309549363234 1877081269 B", "\x02\x33\x32\xc4\x80\x92\xf2\xd1\xba\x97\xe3\x64\xea\xf2\x8f\xfe\x0d\x84\x01"sv),
+TestCase("%s: %llu %d %c", "33: 3467724524658884800 1223515015 (", "\x02\x33\x33\x80\x83\xa0\xf3\xa2\xc5\xea\x9f\x60\x8e\xee\xea\x8e\x09\x50"sv),
+TestCase("%s: %llu %d %c", "34: 7712429266319135356 1217500709 l", "\x02\x33\x34\xf8\xc9\xba\xe4\xc6\xe3\x86\x88\xd6\x01\xca\xd8\x8c\x89\x09\xd8\x01"sv),
+TestCase("%s: %llu %d %c", "35: 3291981441856444913 1855334785 y", "\x02\x33\x35\xe2\xe7\xc0\x90\xc0\xeb\xbb\xaf\x5b\x82\xa6\xb1\xe9\x0d\xf2\x01"sv),
+TestCase("%s: %llu %d %c", "36: 4919847686728209916 715097219 :", "\x02\x33\x36\xf8\xd7\xb2\xc7\xf2\xd9\xe7\xc6\x88\x01\x86\x92\xfc\xa9\x05\x74"sv),
+TestCase("%s: %llu %d %c", "37: 6001314382948090489 1275341105 ]", "\x02\x33\x37\xf2\x89\x82\xd1\xd1\xe8\xf9\xc8\xa6\x01\xe2\xa4\xa1\xc0\x09\xba\x01"sv),
+TestCase("%s: %llu %d %c", "38: 3119268858057174477 692013498 2", "\x02\x33\x38\x9a\xb7\xfd\xb6\xfe\x9f\xef\xc9\x56\xf4\xa6\xfa\x93\x05\x64"sv),
+TestCase("%s: %llu %d %c", "39: 3141952960809904282 1723961557 I", "\x02\x33\x39\xb4\xa2\xfd\xa0\xc6\xe4\xba\x9a\x57\xaa\xc3\x8c\xec\x0c\x92\x01"sv),
+TestCase("%s: %llu %d %c", "40: 3962329461475475834 2013070724 s", "\x02\x34\x30\xf4\x85\xfc\xa4\xdc\xe9\x82\xfd\x6d\x88\x96\xe8\xff\x0e\xe6\x01"sv),
+TestCase("%s: %llu %d %c", "41: 6705183005166907218 2031346987 f", "\x02\x34\x31\xa4\xbd\xad\x8f\xdd\x9a\xcc\x8d\xba\x01\xd6\x94\x9f\x91\x0f\xcc\x01"sv),
+TestCase("%s: %llu %d %c", "42: 5275577307833715720 1461891095 B", "\x02\x34\x32\x90\xb0\xd2\xa8\x87\xe7\xce\xb6\x92\x01\xae\xc0\x95\xf2\x0a\x84\x01"sv),
+TestCase("%s: %llu %d %c", "43: 4999233507689781945 991216024 |", "\x02\x34\x33\xf2\xea\xa2\xf1\xeb\x98\xec\xe0\x8a\x01\xb0\x86\xa6\xb1\x07\xf8\x01"sv),
+TestCase("%s: %llu %d %c", "44: 2788213596089450528 1662775838 '", "\x02\x34\x34\xc0\x80\x9c\x8d\xc7\xdf\xdc\xb1\x4d\xbc\xc8\xdf\xb1\x0c\x4e"sv),
+TestCase("%s: %llu %d %c", "45: 1507235571856201971 690944234 &", "\x02\x34\x35\xe6\xe3\x92\xd5\x8a\xb1\xe3\xea\x29\xd4\xe3\xf7\x92\x05\x4c"sv),
+TestCase("%s: %llu %d %c", "46: 8120818212305491728 1991854726 U", "\x02\x34\x36\xa0\xdc\xee\xdf\xeb\xc8\xf8\xb2\xe1\x01\x8c\xaa\xca\xeb\x0e\xaa\x01"sv),
+TestCase("%s: %llu %d %c", "47: 850107129610567275 826171792 A", "\x02\x34\x37\xd6\xf9\xb7\x97\xc5\xfb\x97\xcc\x17\xa0\x86\xf3\x93\x06\x82\x01"sv),
+TestCase("%s: %llu %d %c", "48: 3619717899166040237 5848102 [", "\x02\x34\x38\xda\x82\xd2\xee\x89\x90\xe9\xbb\x64\xcc\xf0\xc9\x05\xb6\x01"sv),
+TestCase("%s: %llu %d %c", "49: 4766478454387430761 938318292 '", "\x02\x34\x39\xd2\xd5\x86\xf5\xcf\xb9\xf7\xa5\x84\x01\xa8\xe7\xec\xfe\x06\x4e"sv),
+TestCase("%s: %llu %d %c", "50: 7278057998974406311 2037680599 q", "\x02\x35\x30\xce\xba\xfa\xb8\x8e\xcc\xed\x80\xca\x01\xae\xa7\xa4\x97\x0f\xe2\x01"sv),
+TestCase("%s: %llu %d %c", "51: 3273441488341945355 1215440713 U", "\x02\x35\x31\x96\xb0\xae\x9a\x96\xec\xcc\xed\x5a\x92\x9d\x91\x87\x09\xaa\x01"sv),
+TestCase("%s: %llu %d %c", "52: 1352148195246416250 1195236094 E", "\x02\x35\x32\xf4\x85\xc9\xd5\xd3\xe7\xe5\xc3\x25\xfc\xeb\xee\xf3\x08\x8a\x01"sv),
+TestCase("%s: %llu %d %c", "53: 1676322249364352341 1204736997 m", "\x02\x35\x33\xaa\xb5\x91\xa7\x89\x8d\xbf\xc3\x2e\xca\xcf\xf6\xfc\x08\xda\x01"sv),
+TestCase("%s: %llu %d %c", "54: 2333671424671038513 1489186116 [", "\x02\x35\x34\xe2\xf0\xf9\x9f\xfc\xf2\xee\xe2\x40\x88\xb5\x99\x8c\x0b\xb6\x01"sv),
+TestCase("%s: %llu %d %c", "55: 4281900075145433102 1885273938 ~", "\x02\x35\x35\x9c\xc0\x98\xec\xd1\xdb\xae\xec\x76\xa4\xfd\xf7\x85\x0e\xfc\x01"sv),
+TestCase("%s: %llu %d %c", "56: 1034989118514460270 670362370 O", "\x02\x35\x36\xdc\xd9\x97\xb7\xd4\xc7\x82\xdd\x1c\x84\xac\xa7\xff\x04\x9e\x01"sv),
+TestCase("%s: %llu %d %c", "57: 2474224803315291647 852899224 h", "\x02\x35\x37\xfe\xd7\x8c\x81\xb0\x96\x9b\xd6\x44\xb0\xd6\xb1\xad\x06\xd0\x01"sv),
+TestCase("%s: %llu %d %c", "58: 6086297047179889204 792444130 G", "\x02\x35\x38\xe8\xa8\xd4\xea\xde\xba\xef\xf6\xa8\x01\xc4\xf3\xdd\xf3\x05\x8e\x01"sv),
+TestCase("%s: %llu %d %c", "59: 4129732865765686804 1968869102 U", "\x02\x35\x39\xa8\xf8\xc1\x93\xa8\x8a\xe1\xcf\x72\xdc\xbb\xd4\xd5\x0e\xaa\x01"sv),
+TestCase("%s: %llu %d %c", "60: 8297459921109698914 642375500 A", "\x02\x36\x30\xc4\xa5\xc6\xfc\xc3\xf5\xbf\xa6\xe6\x01\x98\xfd\xce\xe4\x04\x82\x01"sv),
+TestCase("%s: %llu %d %c", "61: 8848352886883606505 278420198 )", "\x02\x36\x31\xd2\xff\xfe\xdf\xbd\x84\xd5\xcb\xf5\x01\xcc\xeb\xc2\x89\x02\x52"sv),
+TestCase("%s: %llu %d %c", "62: 639367729021931858 519678154 p", "\x02\x36\x32\xa4\xc5\x9a\x93\xfc\xe3\xbe\xdf\x11\x94\xa3\xcd\xef\x03\xe0\x01"sv),
+TestCase("%s: %llu %d %c", "63: 4168177859746898377 293410050 {", "\x02\x36\x33\x92\x87\xe5\xad\xbe\xeb\xab\xd8\x73\x84\xd4\xe8\x97\x02\xf6\x01"sv),
+TestCase("%s: %llu %d %c", "64: 8933020158994285890 1727299316 n", "\x02\x36\x34\x84\x95\x90\xc4\xac\xa0\xbb\xf8\xf7\x01\xe8\xfb\xa3\xef\x0c\xdc\x01"sv),
+TestCase("%s: %llu %d %c", "65: 2904831533300168615 1786519136 V", "\x02\x36\x35\xce\xde\xad\xab\xf3\xb8\x84\xd0\x50\xc0\xf9\xe0\xa7\x0d\xac\x01"sv),
+TestCase("%s: %llu %d %c", "66: 625118648746706096 1778893413 Q", "\x02\x36\x36\xe0\x92\xa8\x90\xab\x86\xef\xac\x11\xca\x89\xbe\xa0\x0d\xa2\x01"sv),
+TestCase("%s: %llu %d %c", "67: 3317249430279903162 738620162 '", "\x02\x36\x37\xf4\xde\x9a\xb3\x80\xb2\x9e\x89\x5c\x84\xcc\xb3\xc0\x05\x4e"sv),
+TestCase("%s: %llu %d %c", "68: 2529759202900350655 1638933594 =", "\x02\x36\x38\xfe\xda\xb4\x82\xf0\xa5\xc1\x9b\x46\xb4\x91\x81\x9b\x0c\x7a"sv),
+TestCase("%s: %llu %d %c", "69: 4162847733300040370 641234596 `", "\x02\x36\x39\xe4\xca\x9c\xd6\xa5\xfd\xb3\xc5\x73\xc8\xda\xc3\xe3\x04\xc0\x01"sv),
+TestCase("%s: %llu %d %c", "70: 8048952107112912959 1411687739 _", "\x02\x37\x30\xfe\xe0\xc6\xe9\xfa\xd2\xcf\xb3\xdf\x01\xf6\x94\xa5\xc2\x0a\xbe\x01"sv),
+TestCase("%s: %llu %d %c", "71: 1860779983819210237 1258094565 Q", "\x02\x37\x31\xfa\xf7\x9b\xcb\xaa\xe2\xe8\xd2\x33\xca\xff\xe7\xaf\x09\xa2\x01"sv),
+TestCase("%s: %llu %d %c", "72: 133586295505940516 1385623828 t", "\x02\x37\x32\xc8\xc0\xef\xa7\xe5\x81\xcc\xda\x03\xa8\xc4\xb7\xa9\x0a\xe8\x01"sv),
+TestCase("%s: %llu %d %c", "73: 476590412526583632 1426152233 	", "\x02\x37\x33\xa0\xfd\x87\xa0\x90\x9d\x98\x9d\x0d\xd2\xec\x8a\xd0\x0a\x12"sv),
+TestCase("%s: %llu %d %c", "74: 7182329075191030773 870570930 }", "\x02\x37\x34\xea\x8f\xc5\xab\xb0\x8f\xe1\xac\xc7\x01\xe4\xee\x9e\xbe\x06\xfa\x01"sv),
+TestCase("%s: %llu %d %c", "75: 6781911602451840052 1186936344 Z", "\x02\x37\x35\xe8\xf0\xef\xa3\xf3\xaa\x98\x9e\xbc\x01\xb0\xd8\xf9\xeb\x08\xb4\x01"sv),
+TestCase("%s: %llu %d %c", "76: 2755779697069684711 1068026786 W", "\x02\x37\x36\xce\xdf\x93\xb1\x94\xc2\xbf\xbe\x4c\xc4\xae\xc6\xfa\x07\xae\x01"sv),
+TestCase("%s: %llu %d %c", "77: 7605101363071432517 182922063 S", "\x02\x37\x37\x8a\xdd\xb1\xab\xad\xd9\xdf\x8a\xd3\x01\x9e\xad\xb9\xae\x01\xa6\x01"sv),
+TestCase("%s: %llu %d %c", "78: 8440954818933111077 809999951 +", "\x02\x37\x38\xca\xe4\x89\xba\xf1\xeb\xa5\xa4\xea\x01\x9e\xf9\xbc\x84\x06\x56"sv),
+TestCase("%s: %llu %d %c", "79: 177390365453428882 521931632 *", "\x02\x37\x39\xa4\xa2\xed\xf6\x9e\xe6\x9b\xf6\x04\xe0\xad\xe0\xf1\x03\x54"sv),
+TestCase("%s: %llu %d %c", "80: 2690515256289614444 1230288283 #", "\x02\x38\x30\xd8\xe9\xca\x9e\xdc\xd7\xd0\xd6\x4a\xb6\xd6\xa5\x95\x09\x46"sv),
+TestCase("%s: %llu %d %c", "81: 7345613454774364586 1636244573 z", "\x02\x38\x31\xd4\xa6\xbf\xd2\x96\xa1\xee\xf0\xcb\x01\xba\xf1\xb8\x98\x0c\xf4\x01"sv),
+TestCase("%s: %llu %d %c", "82: 6370586723714568954 1353978889 X", "\x02\x38\x32\xf4\xcb\x9c\xdd\xea\xb7\xef\xe8\xb0\x01\x92\xd0\xa0\x8b\x0a\xb0\x01"sv),
+TestCase("%s: %llu %d %c", "83: 731720363738568228 1838212903 J", "\x02\x38\x33\xc8\xd8\xf4\xb8\xe8\xf2\xcb\xa7\x14\xce\x9c\x87\xd9\x0d\x94\x01"sv),
+TestCase("%s: %llu %d %c", "84: 9161537274294991160 1309917278 u", "\x02\x38\x34\xf0\xe4\xd3\xed\x8d\xe7\xa8\xa4\xfe\x01\xbc\x81\x9e\xe1\x09\xea\x01"sv),
+TestCase("%s: %llu %d %c", "85: 1558715097695343451 97413929 Z", "\x02\x38\x35\xb6\xcd\xda\x82\xef\xc7\xd5\xa1\x2b\xd2\xac\xf3\x5c\xb4\x01"sv),
+TestCase("%s: %llu %d %c", "86: 366808135709449019 1760066788 z", "\x02\x38\x36\xf6\xec\xfa\xda\xca\x83\x95\x97\x0a\xc8\xf3\xc3\x8e\x0d\xf4\x01"sv),
+TestCase("%s: %llu %d %c", "87: 891656539731055874 742154760 1", "\x02\x38\x37\x84\xd4\x96\x85\xc2\xb9\xe6\xdf\x18\x90\x88\xe3\xc3\x05\x62"sv),
+TestCase("%s: %llu %d %c", "88: 3732114429930137548 1005229045 2", "\x02\x38\x38\x98\x9f\xc3\x91\x9d\x93\x91\xcb\x67\xea\xcf\xd4\xbe\x07\x64"sv),
+TestCase("%s: %llu %d %c", "89: 8113837181842537067 1082098316 G", "\x02\x38\x39\xd6\xe9\xa5\xd1\xb1\xfb\x91\x9a\xe1\x01\x98\x8a\xfc\x87\x08\x8e\x01"sv),
+TestCase("%s: %llu %d %c", "90: 2770024080011047550 1330821140 N", "\x02\x39\x30\xfc\xc9\xb1\xc5\xaf\x8e\x8d\xf1\x4c\xa8\xe0\x95\xf5\x09\x9c\x01"sv),
+TestCase("%s: %llu %d %c", "91: 4697198541546045125 2128296134 x", "\x02\x39\x31\x8a\xfb\xad\xc5\xf9\xcb\xe6\xaf\x82\x01\x8c\xe3\xd9\xed\x0f\xf0\x01"sv),
+TestCase("%s: %llu %d %c", "92: 1018100739973888361 570654735 w", "\x02\x39\x32\xd2\xe5\xa5\xfb\xd2\xce\x82\xa1\x1c\x9e\x80\x9c\xa0\x04\xee\x01"sv),
+TestCase("%s: %llu %d %c", "93: 4859753067646279323 676264182 W", "\x02\x39\x33\xb6\xfa\xde\xd2\xd7\xea\xa7\xf1\x86\x01\xec\xe3\xf7\x84\x05\xae\x01"sv),
+TestCase("%s: %llu %d %c", "94: 549670438443165922 132670573 t", "\x02\x39\x34\xc4\xa3\x81\xaf\xbf\x96\xe9\xa0\x0f\xda\x91\xc3\x7e\xe8\x01"sv),
+TestCase("%s: %llu %d %c", "95: 3444356383434812300 501045255 2", "\x02\x39\x35\x98\xae\xd9\xa7\xb7\xf8\xe7\xcc\x5f\x8e\xe0\xea\xdd\x03\x64"sv),
+TestCase("%s: %llu %d %c", "96: 2011546512680833085 1077631988 Z", "\x02\x39\x36\xfa\xc0\xa2\x98\xa9\xb9\xb9\xea\x37\xe8\xef\xda\x83\x08\xb4\x01"sv),
+TestCase("%s: %llu %d %c", "97: 4692262078416922470 1829700092 '", "\x02\x39\x37\xcc\x9d\x9d\x91\xfd\xdf\xa1\x9e\x82\x01\xf8\x87\xf8\xd0\x0d\x4e"sv),
+TestCase("%s: %llu %d %c", "98: 4919691286333696629 1322133483 l", "\x02\x39\x38\xea\xa9\x89\xe8\x98\xca\xa0\xc6\x88\x01\xd6\x9f\xf1\xec\x09\xd8\x01"sv),
+TestCase("%s: %llu %d %c", "99: 1241746909443220722 1232171669 L", "\x02\x39\x39\xe4\x93\xeb\xe4\x85\x8f\xc9\xbb\x22\xaa\xca\x8b\x97\x09\x98\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "0: 1941514050836526867 0x      21293775377EEBB901 101", "\x01\x30\xa6\xdc\xd3\xe8\xc3\xaf\xd2\xf1\x35\x82\xe4\xdd\xee\x0f\x82\xe4\xdd\xee\x0f\xca\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "1: 3509957387132194637 0x      19171154647244DC48 33", "\x01\x31\x9a\xbd\xae\xcc\xb4\xe9\xef\xb5\x61\x90\xf1\xa6\xa4\x0e\x90\xf1\xa6\xa4\x0e\x42"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "2: 8024983545482782384 0x      15299301625B30E1B2 46", "\x01\x32\xe0\x9a\xd9\xcd\x86\x81\xbc\xde\xde\x01\xe4\x86\x87\xb3\x0b\xe4\x86\x87\xb3\x0b\x5c"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "3: 5058360797369425151 0x      10725304213FED83F5 104", "\x01\x33\xfe\xf3\xba\x8f\x89\x96\xf4\xb2\x8c\x01\xea\x8f\xec\xfe\x07\xea\x8f\xec\xfe\x07\xd0\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "4: 6529118911438932678 0x       9786230773A549A65 105", "\x01\x34\x8c\xab\xd2\xc9\xd4\xc3\x8b\x9c\xb5\x01\xca\xe9\xa4\xa5\x07\xca\xe9\xa4\xa5\x07\xd2\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "5: 4463425652718897265 0x        17497759010AFE9F 73", "\x01\x35\xe2\xa1\xe8\xfa\xf3\xfe\xa2\xf1\x7b\xbe\xfa\xd7\x10\xbe\xfa\xd7\x10\x92\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "6: 5434900075521908661 0x      123595425449AB2A4E 45", "\x01\x36\xea\xfe\xd0\xf3\xd8\xb4\xd2\xec\x96\x01\x9c\xa9\xd9\x9a\x09\x9c\xa9\xd9\x9a\x09\x5a"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "7: 715267470600062067 0x      120413030347C591FF 114", "\x01\x37\xe6\x91\x92\x88\xb2\xfe\x91\xed\x13\xfe\xc7\xac\xfc\x08\xfe\xc7\xac\xfc\x08\xe4\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "8: 3926011308434788247 0x      12666770164B7FF518 85", "\x01\x38\xae\xbe\xe0\xf6\xfd\x9e\xff\xfb\x6c\xb0\xd4\xff\xb7\x09\xb0\xd4\xff\xb7\x09\xaa\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "9: 1478056893002169192 0x       7358327362BDBEAA0 35", "\x01\x39\xd0\xed\xb1\x93\xb8\xba\x8e\x83\x29\xc0\xaa\xdf\xbd\x05\xc0\xaa\xdf\xbd\x05\x46"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "10: 7128491551588143718 0x      1717398262665D6AF6 94", "\x02\x31\x30\xcc\xc9\xc6\xcc\x87\xd3\xbe\xed\xc5\x01\xec\xab\xeb\xe5\x0c\xec\xab\xeb\xe5\x0c\xbc\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "11: 193231861624509923 0x       4552840211B231535 41", "\x02\x31\x31\xc6\xd7\xd4\xf6\xd6\xd6\xbf\xae\x05\xea\xd4\x98\xb2\x03\xea\xd4\x98\xb2\x03\x52"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "12: 4888125773563526975 0x      1378744602522DF91A 110", "\x02\x31\x32\xfe\xfc\xfb\xea\xc3\x9f\x8e\xd6\x87\x01\xb4\xe4\xef\xa2\x0a\xb4\xe4\xef\xa2\x0a\xdc\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "13: 7673779374634366645 0x        1131769900ACB1C3 60", "\x02\x31\x33\xea\xda\xe7\xdc\xe1\xeb\xde\xfe\xd4\x01\x86\xc7\xe5\x0a\x86\xc7\xe5\x0a\x78"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "14: 5854341954344571365 0x      108816167440DC078A 107", "\x02\x31\x34\xca\xc7\xfe\xe5\xac\xbf\xe6\xbe\xa2\x01\x94\x9e\xe0\x8d\x08\x94\x9e\xe0\x8d\x08\xd6\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "15: 2326184191199164007 0x       8137466533080C9DD 82", "\x02\x31\x35\xce\xd9\xf1\xe8\xcf\x8c\xa2\xc8\x40\xba\xa7\x86\x88\x06\xba\xa7\x86\x88\x06\xa4\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "16: 1424330851786829530 0x      1208787250480CA132 57", "\x02\x31\x36\xb4\xbb\x91\xf6\x9f\xd7\x9e\xc4\x27\xe4\x84\xe5\x80\x09\xe4\x84\xe5\x80\x09\x72"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "17: 6389735809259880362 0x      18144454646C263D98 42", "\x02\x31\x37\xd4\xfe\xb9\xf8\xa7\xb7\xf3\xac\xb1\x01\xb0\xf6\xb1\xc2\x0d\xb0\xf6\xb1\xc2\x0d\x54"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "18: 4564825427452770815 0x       2195709200D1662E8 62", "\x02\x31\x38\xfe\xe7\xf1\x8f\xce\xa2\xc2\xd9\x7e\xd0\x8b\xb3\xd1\x01\xd0\x8b\xb3\xd1\x01\x7c"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "19: 1621021906259841134 0x      177653957669E3D7B8 93", "\x02\x31\x39\xdc\xc1\xf4\xfb\xb9\xb5\x83\xff\x2c\xf0\xde\x9e\x9e\x0d\xf0\xde\x9e\x9e\x0d\xba\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "20: 8025284571884195522 0x      13595901875109B32B 94", "\x02\x32\x30\x84\xbb\xa4\x89\x8c\xf3\xc4\xdf\xde\x01\xd6\xcc\xcd\x90\x0a\xd6\xcc\xcd\x90\x0a\xbc\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "21: 1310759272316304459 0x       85298242232D77A96 119", "\x02\x32\x31\x96\xf1\xb3\xc7\xa1\xa7\xe0\xb0\x24\xac\xea\xbb\xad\x06\xac\xea\xbb\xad\x06\xee\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "22: 3661335273992329249 0x       825503981313430ED 105", "\x02\x32\x32\xc2\xe0\xdc\x9e\x8f\xc2\xd6\xcf\x65\xda\xc3\xa1\x93\x06\xda\xc3\xa1\x93\x06\xd2\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "23: 4908269103466540013 0x      20984305627D138262 115", "\x02\x32\x33\xda\xaf\xaf\xe2\xd2\xaf\xd6\x9d\x88\x01\xc4\x89\x9c\xd1\x0f\xc4\x89\x9c\xd1\x0f\xe6\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "24: 6541851858139706195 0x      148818392458B3E274 57", "\x02\x32\x34\xa6\xcd\xab\x9a\xda\xe6\xa9\xc9\xb5\x01\xe8\x89\x9f\x8b\x0b\xe8\x89\x9f\x8b\x0b\x72"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "25: 4510904210489015127 0x      1191906481470B0CB1 71", "\x02\x32\x35\xae\xed\x97\xa0\xd9\xde\xf9\x99\x7d\xe2\xb2\xd8\xf0\x08\xe2\xb2\xd8\xf0\x08\x8e\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "26: 1751202991906984173 0x      10412118623E0FA1D6 115", "\x02\x32\x36\xda\xc3\xbc\xe3\xf8\xf5\xc2\xcd\x30\xac\x87\xfd\xe0\x07\xac\x87\xfd\xe0\x07\xe6\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "27: 7912743439818658557 0x      15466272805C2FA8D0 112", "\x02\x32\x37\xfa\xcb\x8c\x93\x85\x8f\xdb\xcf\xdb\x01\xa0\xa3\xfd\xc2\x0b\xa0\xa3\xfd\xc2\x0b\xe0\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "28: 7865046769076850396 0x       95679371239078370 56", "\x02\x32\x38\xb8\xbb\xc6\xca\xb3\x97\xa1\xa6\xda\x01\xe0\x8d\xbc\x90\x07\xe0\x8d\xbc\x90\x07\x70"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "29: 436719658465345699 0x       5945969062370D42A 43", "\x02\x32\x39\xc6\x82\x8e\xd4\xe5\x8d\xc5\x8f\x0c\xd4\xd0\x86\xb7\x04\xd4\xd0\x86\xb7\x04\x56"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "30: 6212224561138197241 0x        1234914600BC6EDA 87", "\x02\x33\x30\xf2\x8b\xcb\xf9\xe2\xd4\xa0\xb6\xac\x01\xb4\xbb\xe3\x0b\xb4\xbb\xe3\x0b\xae\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "31: 5419660422277913790 0x      177818160669FCE5E6 39", "\x02\x33\x31\xfc\xf2\xf2\x91\x90\x9c\xc0\xb6\x96\x01\xcc\x97\xe7\x9f\x0d\xcc\x97\xe7\x9f\x0d\x4e"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "32: 1955771002711010609 0x      175837101468CE9CC6 114", "\x02\x33\x32\xe2\xb4\xcb\xe9\xac\xd7\xa5\xa4\x36\x8c\xf3\xf4\x8c\x0d\x8c\xf3\xf4\x8c\x0d\xe4\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "33: 4550116504720438569 0x       1763101400A82477C 115", "\x02\x33\x33\xd2\xd4\x87\xe8\xcf\xb6\xa1\xa5\x7e\xf8\x9d\x92\xa8\x01\xf8\x9d\x92\xa8\x01\xe6\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "34: 4684454901623556905 0x       4891671541D281932 106", "\x02\x33\x34\xd2\xac\xe0\x99\xbd\xba\xc3\x82\x82\x01\xe4\xe4\xc0\xd2\x03\xe4\xe4\xc0\xd2\x03\xd4\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "35: 6234369280162760629 0x       39351496417748FD4 46", "\x02\x33\x35\xea\x9e\xac\xeb\x88\xf5\xf6\x84\xad\x01\xa8\xbf\xa4\xf7\x02\xa8\xbf\xa4\xf7\x02\x5c"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "36: 8944940508995398022 0x      137633313452092D4E 45", "\x02\x33\x36\x8c\xa6\xe1\xfa\x84\x80\xe8\xa2\xf8\x01\x9c\xb5\xc9\xa0\x0a\x9c\xb5\xc9\xa0\x0a\x5a"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "37: 4077388965675931531 0x       2899199101147D3A6 47", "\x02\x33\x37\x96\xde\xb0\xe3\xa9\xea\xe5\x95\x71\xcc\xce\xbe\x94\x02\xcc\xce\xbe\x94\x02\x5e"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "38: 8856210144434134086 0x      1990062769769DF2B1 83", "\x02\x33\x38\x8c\xf1\xa8\xb3\x88\x8d\xca\xe7\xf5\x01\xe2\xca\xef\xe9\x0e\xe2\xca\xef\xe9\x0e\xa6\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "39: 6269182192420305924 0x      15904663025ECC96FE 114", "\x02\x33\x39\x88\xf0\xd5\xb6\xa1\xff\xcd\x80\xae\x01\xfc\xdb\xe4\xec\x0b\xfc\xdb\xe4\xec\x0b\xe4\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "40: 3168030316008523010 0x       560637674216AA6EA 54", "\x02\x34\x30\x84\x94\xda\xf5\xb2\xb2\x8d\xf7\x57\xd4\x9b\xd5\x96\x04\xd4\x9b\xd5\x96\x04\x6c"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "41: 7828361129715530032 0x      120199790547A50851 125", "\x02\x34\x31\xe0\xe4\xdb\xe2\x92\xbe\xf6\xa3\xd9\x01\xa2\xa1\xa8\xfa\x08\xa2\xa1\xa8\xfa\x08\xfa\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "42: 4684430121285811332 0x      1645956019621B4BB3 49", "\x02\x34\x32\x88\xf2\x8b\xa6\x89\x98\xb8\x82\x82\x01\xe6\xae\xda\xa1\x0c\xe6\xae\xda\xa1\x0c\x62"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "43: 1094908319066476988 0x      200784098877AD38DC 34", "\x02\x34\x33\xf8\xc6\x9e\xb9\x95\xd4\xf2\xb1\x1e\xb8\xe3\xe9\xfa\x0e\xb8\xe3\xe9\xfa\x0e\x44"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "44: 4188361875415362989 0x      193392125773454BE9 109", "\x02\x34\x34\xda\xb6\xdf\xee\xe9\xbb\x86\xa0\x74\xd2\xaf\xaa\xb4\x0e\xd2\xaf\xaa\xb4\x0e\xda\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "45: 6350883509347762576 0x      147404369957DC1F33 76", "\x02\x34\x35\xa0\x86\xfa\x88\xe8\xb9\xef\xa2\xb0\x01\xe6\xfc\xe0\xfd\x0a\xe6\xfc\xe0\xfd\x0a\x98\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "46: 3843711385867305612 0x      17834016666A4C8CC2 61", "\x02\x34\x36\x98\x8a\xf6\xdb\xff\xc8\xcd\xd7\x6a\x84\xb3\xe4\xa4\x0d\x84\xb3\xe4\xa4\x0d\x7a"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "47: 5535789636778214509 0x      1184586398469B5A9E 84", "\x02\x34\x37\xda\x91\xa2\xb7\x88\xd6\x89\xd3\x99\x01\xbc\xea\xda\xe9\x08\xbc\xea\xda\xe9\x08\xa8\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "48: 8891437578007474517 0x       1695359970A1AE9FD 111", "\x02\x34\x38\xaa\xc5\x8f\xbb\xc9\xd7\xdd\xe4\xf6\x01\xfa\xa7\xd7\xa1\x01\xfa\xa7\xd7\xa1\x01\xde\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "49: 2483479174720276235 0x      14806493005840EA54 82", "\x02\x34\x39\x96\x9c\xc2\x84\xd8\xc9\x8b\xf7\x44\xa8\xa9\x87\x84\x0b\xa8\xa9\x87\x84\x0b\xa4\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "50: 6416832625845083020 0x      13297388064F423436 87", "\x02\x35\x30\x98\xee\x90\xea\xcf\xd1\x95\x8d\xb2\x01\xec\xd0\x91\xf4\x09\xec\xd0\x91\xf4\x09\xae\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "51: 3612059979445064775 0x      20936993057CCB50E9 117", "\x02\x35\x31\x8e\xd1\xd2\xe3\xbd\xda\xce\xa0\x64\xd2\xc3\xda\xcc\x0f\xd2\xc3\xda\xcc\x0f\xea\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "52: 8599514281591661754 0x        1468170900E0066D 118", "\x02\x35\x32\xf4\x92\xdb\xb2\xa9\xab\xce\xd7\xee\x01\xda\x99\x80\x0e\xda\x99\x80\x0e\xec\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "53: 1367741239369008397 0x         531248300510FE3 73", "\x02\x35\x33\x9a\x94\xf0\xd3\xa3\xda\x98\xfb\x25\xc6\xbf\x88\x05\xc6\xbf\x88\x05\x92\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "54: 5473904087316166353 0x       5067733201E34BF48 68", "\x02\x35\x34\xa2\xdb\xcf\xb0\xfe\xb0\x9b\xf7\x97\x01\x90\xfd\xa5\xe3\x03\x90\xfd\xa5\xe3\x03\x88\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "55: 1822527202856628083 0x      15610026845D0B02BC 58", "\x02\x35\x35\xe6\xbd\x8b\xfc\xb6\xb5\xf5\xca\x32\xf8\x8a\xd8\xd0\x0b\xf8\x8a\xd8\xd0\x0b\x74"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "56: 3032341809633898873 0x      10226365813CF43225 113", "\x02\x35\x36\xf2\xc5\xd9\x8c\xea\xb3\x85\x95\x54\xca\xc8\xa1\xcf\x07\xca\xc8\xa1\xcf\x07\xe2\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "57: 6856757614633255152 0x        8231254204E7FD5E 91", "\x02\x35\x37\xe0\x83\xdc\xb2\x99\xae\x8c\xa8\xbe\x01\xbc\xf5\xbf\x4e\xbc\xf5\xbf\x4e\xb6\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "58: 3224702228554841730 0x      189119246370B94E8F 92", "\x02\x35\x38\x84\xfa\xdf\xf6\xee\xe5\xb8\xc0\x59\x9e\xba\xca\x8b\x0e\x9e\xba\xca\x8b\x0e\xb8\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "59: 3988658283410987938 0x      176694389369516C95 34", "\x02\x35\x39\xc4\xee\x8c\x92\xee\xe4\xc7\xda\x6e\xaa\xb2\x8b\x95\x0d\xaa\xb2\x8b\x95\x0d\x44"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "60: 72920210115635164 0x      1901935881715D3D09 71", "\x02\x36\x30\xb8\x9f\x9d\xce\xee\xa2\x88\x83\x02\x92\xf4\xe9\x95\x0e\x92\xf4\xe9\x95\x0e\x8e\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "61: 4655403017017936000 0x      19053934447191FF24 48", "\x02\x36\x31\x80\xa2\x96\xba\x9d\x98\xa8\x9b\x81\x01\xc8\xfc\x8f\x99\x0e\xc8\xfc\x8f\x99\x0e\x60"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "62: 288952216557681443 0x       39039797517450017 85", "\x02\x36\x32\xc6\xec\xdb\x8f\xd2\xa0\xc8\x82\x08\xae\x80\xa8\xf4\x02\xae\x80\xa8\xf4\x02\xaa\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "63: 3219359012813218523 0x      21243024217E9E4855 51", "\x02\x36\x33\xb6\x8b\xf9\xb2\xe3\xfd\xba\xad\x59\xaa\xa1\xf2\xe9\x0f\xaa\xa1\xf2\xe9\x0f\x66"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "64: 7496923004744506260 0x       65433043327004A41 39", "\x02\x36\x34\xa8\xee\x80\xe4\x93\xf0\xb5\x8a\xd0\x01\x82\xa9\x82\xf0\x04\x82\xa9\x82\xf0\x04\x4e"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "65: 5886875793686089828 0x       2428972020E7A5132 73", "\x02\x36\x35\xc8\xc1\x8a\xf8\x84\x96\xb1\xb2\xa3\x01\xe4\xc4\xd2\xe7\x01\xe4\xc4\xd2\xe7\x01\x92\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "66: 8352418169916257811 0x       157093077095D0CD5 107", "\x02\x36\x36\xa6\xd8\x83\x96\xd2\x84\xe0\xe9\xe7\x01\xaa\xb3\xe8\x95\x01\xaa\xb3\xe8\x95\x01\xd6\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "67: 6425607598961991251 0x      142445717254E77DD4 102", "\x02\x36\x37\xa6\xf9\xf0\xa2\xa4\x84\xac\xac\xb2\x01\xa8\xf7\xbb\xce\x0a\xa8\xf7\xbb\xce\x0a\xcc\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "68: 2394571431170796410 0x      21402538797F91AEB7 105", "\x02\x36\x38\xf4\xad\xdf\x83\xf3\x81\x9d\xbb\x42\xee\xba\x8d\xf9\x0f\xee\xba\x8d\xf9\x0f\xd2\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "69: 891164515808164858 0x      10639639773F6ACD49 125", "\x02\x36\x39\xf4\xef\xed\x92\xfa\xd9\x86\xde\x18\x92\xb5\xd6\xf6\x07\x92\xb5\xd6\xf6\x07\xfa\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "70: 4245075167591901055 0x      21000887517D2CCFAF 64", "\x02\x37\x30\xfe\x9d\xad\x92\xf4\xd7\xc4\xe9\x75\xde\xbe\xe6\xd2\x0f\xde\xbe\xe6\xd2\x0f\x80\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "71: 979687222629424543 0x       958033112391A6CD8 45", "\x02\x37\x31\xbe\xf6\x86\xfd\xd3\x95\xc6\x98\x1b\xb0\xb3\xd3\x91\x07\xb0\xb3\xd3\x91\x07\x5a"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "72: 2602113737417537288 0x       93725236837DD5610 105", "\x02\x37\x32\x90\xdc\xfd\xfe\x8a\xaa\xc8\x9c\x48\xa0\xd8\xea\xfd\x06\xa0\xd8\xea\xfd\x06\xd2\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "73: 3036291044299697402 0x       2492532650EDB4D91 53", "\x02\x37\x33\xf4\xb3\xf7\xa2\xc5\xa7\x89\xa3\x54\xa2\xb6\xda\xed\x01\xa2\xb6\xda\xed\x01\x6a"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "74: 5631405388638357750 0x        1248216000BE7670 56", "\x02\x37\x34\xec\xa3\x94\x80\xaa\xd7\xe2\xa6\x9c\x01\xe0\xd9\xf3\x0b\xe0\xd9\xf3\x0b\x70"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "75: 6477169593541844553 0x       41341392918A43229 54", "\x02\x37\x35\x92\xf9\xae\xd0\xb0\xdb\xc3\xe3\xb3\x01\xd2\xc8\xa1\x8a\x03\xd2\xc8\xa1\x8a\x03\x6c"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "76: 720129488086281194 0x      15625894285D2338F4 83", "\x02\x37\x36\xd4\xaf\xd5\x9a\x87\xfd\xb4\xfe\x13\xe8\xe3\x99\xd2\x0b\xe8\xe3\x99\xd2\x0b\xa6\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "77: 7513165068981918756 0x       694699741296846DD 57", "\x02\x37\x37\xc8\xf0\xd2\xb8\xdc\xf4\x8f\xc4\xd0\x01\xba\x9b\xc2\x96\x05\xba\x9b\xc2\x96\x05\x72"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "78: 3737216259576487553 0x       5295090401F8FAAB0 121", "\x02\x37\x38\x82\xfa\xf6\x8d\xe6\x98\xa1\xdd\x67\xe0\xaa\xfd\xf8\x03\xe0\xaa\xfd\xf8\x03\xf2\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "79: 3565198557459535882 0x       86912895033CDDAF6 125", "\x02\x37\x39\x94\xe0\xe2\xb1\xdc\xcc\x90\xfa\x62\xec\xeb\xee\xbc\x06\xec\xeb\xee\xbc\x06\xfa\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "80: 5371965991102827603 0x       11276814506B8B491 120", "\x02\x38\x30\xa6\xb1\xce\xd2\xec\xa6\x87\x8d\x95\x01\xa2\xd2\xc5\x6b\xa2\xd2\xc5\x6b\xf0\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "81: 5776656679787538668 0x      18268958606CE437F4 72", "\x02\x38\x31\xd8\xa3\xfd\xc1\x9d\xa9\xe7\xaa\xa0\x01\xe8\xdf\xa1\xce\x0d\xe8\xdf\xa1\xce\x0d\x90\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "82: 749331093609658474 0x       96722711439A6B6EA 32", "\x02\x38\x32\xd4\xe1\x91\xdc\x9a\xaa\x94\xe6\x14\xd4\xdb\xb5\x9a\x07\xd4\xdb\xb5\x9a\x07\x40"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "83: 8161063962699005564 0x       86834268433C1DB9C 80", "\x02\x38\x33\xf8\xf9\xd3\xde\xea\x9b\xf6\xc1\xe2\x01\xb8\xee\x8e\xbc\x06\xb8\xee\x8e\xbc\x06\xa0\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "84: 3621720449882747425 0x       85086863932B7399F 122", "\x02\x38\x34\xc2\xc8\xbb\xb0\xef\xe3\xf7\xc2\x64\xbe\xe6\xb9\xab\x06\xbe\xe6\xb9\xab\x06\xf4\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "85: 8747087227033699739 0x      18257513516CD2C137 101", "\x02\x38\x35\xb6\x96\x9a\xab\xa5\xdf\xf2\xe3\xf2\x01\xee\x84\x96\xcd\x0d\xee\x84\x96\xcd\x0d\xca\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "86: 8253451662283365177 0x       7840661982EBBE696 79", "\x02\x38\x36\xf2\xec\x8e\xd4\xdc\xa3\x93\x8a\xe5\x01\xac\x9a\xdf\xeb\x05\xac\x9a\xdf\xeb\x05\x9e\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "87: 134795648044988988 0x       5147834581EAEF8E2 105", "\x02\x38\x37\xf8\xe8\xeb\xef\xaf\xfb\xf1\xde\x03\xc4\xe3\xf7\xea\x03\xc4\xe3\xf7\xea\x03\xd2\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "88: 421750780720578633 0x      137027843451ACCA22 65", "\x02\x38\x38\x92\x81\xc0\xc9\xb7\x86\xae\xda\x0b\xc4\xa8\xe6\x9a\x0a\xc4\xa8\xe6\x9a\x0a\x82\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "89: 8457438023241447265 0x      200824394177B35EE5 47", "\x02\x38\x39\xc2\xdd\x81\xd3\xd3\xc4\xed\xde\xea\x01\xca\xfb\x9a\xfb\x0e\xca\xfb\x9a\xfb\x0e\x5e"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "90: 4522738759120336903 0x       6219044132511821D 72", "\x02\x39\x30\x8e\xe0\x91\xd4\x8b\xbc\xff\xc3\x7d\xba\x88\x8c\xd1\x04\xba\x88\x8c\xd1\x04\x90\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "91: 3110596655769706088 0x      167356506563C09389 68", "\x02\x39\x31\xd0\xf9\xde\xaa\xb0\xcb\x87\xab\x56\x92\xce\x84\xbc\x0c\x92\xce\x84\xbc\x0c\x88\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "92: 6450021345187045785 0x       390795837174B123D 32", "\x02\x39\x32\xb2\x86\xfd\x98\xac\x8f\x8a\x83\xb3\x01\xfa\xc8\xd8\xf4\x02\xfa\xc8\xd8\xf4\x02\x40"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "93: 8586083292869710126 0x      170638545665B56030 78", "\x02\x39\x33\xdc\xc4\xd2\xb6\xf1\xd0\xf2\xa7\xee\x01\xe0\x80\xab\xdb\x0c\xe0\x80\xab\xdb\x0c\x9c\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "94: 88833738086841272 0x       90226635535C77DF3 82", "\x02\x39\x34\xf0\xee\xe3\xab\x8d\xf4\xcc\xbb\x02\xe6\xf7\xbb\xdc\x06\xe6\xf7\xbb\xdc\x06\xa4\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "95: 349662797203771308 0x      18558755186E9E69BE 118", "\x02\x39\x35\xd8\xbe\x80\xa8\xc4\x9d\xa0\xda\x09\xfc\xa6\xf3\xe9\x0d\xfc\xa6\xf3\xe9\x0d\xec\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "96: 163280929539230956 0x       40045642217DE7AE6 89", "\x02\x39\x36\xd8\xb3\xc0\xbc\xfd\xc8\x8b\xc4\x04\xcc\xeb\xf3\xfd\x02\xcc\xeb\xf3\xfd\x02\xb2\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "97: 4371241115470848126 0x      1128261220433FE664 112", "\x02\x39\x37\xfc\x81\xc7\xb3\xcf\xa8\xe2\xa9\x79\xc8\x99\xff\xb3\x08\xc8\x99\xff\xb3\x08\xe0\x01"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "98: 428374782176628108 0x      17127620106616AC9A 60", "\x02\x39\x38\x98\xb6\xfc\xa5\x88\xa6\xf2\xf1\x0b\xb4\xb2\xb5\xe1\x0c\xb4\xb2\xb5\xe1\x0c\x78"sv),
+TestCase("%s: %lld 0x%16u%08X %d", "99: 7023621621475593673 0x       1965201350BB6A8C7 58", "\x02\x39\x39\x92\xb7\xf2\x8c\xdd\xa9\xf5\xf8\xc2\x01\x8e\xa3\xb5\xbb\x01\x8e\xa3\xb5\xbb\x01\x74"sv),
+
+};
+
+}  // namespace pw::test::tokenized_string_decoding
diff --git a/pw_tokenizer/pw_tokenizer_private/varint_decoding_test_data.h b/pw_tokenizer/pw_tokenizer_private/varint_decoding_test_data.h
new file mode 100644
index 0000000..8835f0c
--- /dev/null
+++ b/pw_tokenizer/pw_tokenizer_private/varint_decoding_test_data.h
@@ -0,0 +1,1012 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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.
+
+// AUTOGENERATED - DO NOT EDIT
+// This file contains test data generated by generate_decoding_test_data.cc.
+#pragma once
+
+#include <string_view>
+#include <tuple>
+
+namespace pw::test::varint_decoding {
+
+using namespace std::literals::string_view_literals;
+
+// clang-format off
+using TestCase = std::tuple<const char*, const char*, const char*, const char*, std::string_view>;
+
+inline constexpr TestCase kTestData[] = {
+
+// Important numbers
+TestCase("%d", "0", "%u", "0", "\x00"sv),
+TestCase("%d", "-32768", "%u", "4294934528", "\xff\xff\x03"sv),
+TestCase("%d", "-32767", "%u", "4294934529", "\xfd\xff\x03"sv),
+TestCase("%d", "32766", "%u", "32766", "\xfc\xff\x03"sv),
+TestCase("%d", "32767", "%u", "32767", "\xfe\xff\x03"sv),
+TestCase("%d", "-2147483648", "%u", "2147483648", "\xff\xff\xff\xff\x0f"sv),
+TestCase("%d", "-2147483647", "%u", "2147483649", "\xfd\xff\xff\xff\x0f"sv),
+TestCase("%d", "2147483646", "%u", "2147483646", "\xfc\xff\xff\xff\x0f"sv),
+TestCase("%d", "2147483647", "%u", "2147483647", "\xfe\xff\xff\xff\x0f"sv),
+TestCase("%lld", "-9223372036854775808", "%llu", "9223372036854775808", "\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv),
+TestCase("%lld", "-9223372036854775807", "%llu", "9223372036854775809", "\xfd\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv),
+TestCase("%lld", "9223372036854775806", "%llu", "9223372036854775806", "\xfc\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv),
+TestCase("%lld", "9223372036854775807", "%llu", "9223372036854775807", "\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv),
+
+// Random 64-bit ints
+TestCase("%lld", "5922204476835468009", "%llu", "5922204476835468009", "\xd2\xcb\x8c\x90\x86\xe6\xf2\xaf\xa4\x01"sv),
+TestCase("%lld", "2004795154352895159", "%llu", "2004795154352895159", "\xee\xd2\x87\xea\xc5\xa4\xbb\xd2\x37"sv),
+TestCase("%lld", "7672492112153174982", "%llu", "7672492112153174982", "\x8c\x8f\x83\xee\x9c\xbb\x95\xfa\xd4\x01"sv),
+TestCase("%lld", "6325664365257058358", "%llu", "6325664365257058358", "\xec\xa0\xf3\xb7\xb6\x8e\xa3\xc9\xaf\x01"sv),
+TestCase("%lld", "4553661289274231220", "%llu", "4553661289274231220", "\xe8\xa6\x9a\xea\x9e\xb4\xed\xb1\x7e"sv),
+TestCase("%lld", "6372308406878241426", "%llu", "6372308406878241426", "\xa4\x8a\xb4\xf3\xfd\xae\xfe\xee\xb0\x01"sv),
+TestCase("%lld", "7156998241444634343", "%llu", "7156998241444634343", "\xce\xfb\xb8\xf6\xe5\xfe\xe1\xd2\xc6\x01"sv),
+TestCase("%lld", "1376699938710259787", "%llu", "1376699938710259787", "\x96\xa1\x84\x92\x9b\xd3\x82\x9b\x26"sv),
+TestCase("%lld", "3051600409971083011", "%llu", "3051600409971083011", "\x86\x9c\x9a\x8c\xf4\x99\xbb\xd9\x54"sv),
+TestCase("%lld", "6288685020493584850", "%llu", "6288685020493584850", "\xa4\xe7\xb8\xed\xa1\xed\xf2\xc5\xae\x01"sv),
+TestCase("%lld", "5705831195318701531", "%llu", "5705831195318701531", "\xb6\xb7\x82\x95\xb9\xcf\x97\xaf\x9e\x01"sv),
+TestCase("%lld", "2504359322455446492", "%llu", "2504359322455446492", "\xb8\xcf\xa8\xce\x9f\xe2\xa2\xc1\x45"sv),
+TestCase("%lld", "3679108774547190895", "%llu", "3679108774547190895", "\xde\xd1\xc3\xce\x81\xfc\xe8\x8e\x66"sv),
+TestCase("%lld", "1452704646622358274", "%llu", "1452704646622358274", "\x84\xac\xe1\x97\xbd\xcb\x85\xa9\x28"sv),
+TestCase("%lld", "1846464682573605487", "%llu", "1846464682573605487", "\xde\xa9\xd5\xf1\x90\xf6\xfa\x9f\x33"sv),
+TestCase("%lld", "4528166100111793966", "%llu", "4528166100111793966", "\xdc\xfc\x88\x92\xf5\xc4\xa3\xd7\x7d"sv),
+TestCase("%lld", "8393903718445878140", "%llu", "8393903718445878140", "\xf8\xad\xe0\x8c\xb1\xbd\x91\xfd\xe8\x01"sv),
+TestCase("%lld", "3957962835363152585", "%llu", "3957962835363152585", "\x92\x8b\xa6\xc0\xd0\x8e\xc1\xed\x6d"sv),
+TestCase("%lld", "3190545832108956470", "%llu", "3190545832108956470", "\xec\xec\xa8\xa7\xf6\xa1\x8c\xc7\x58"sv),
+TestCase("%lld", "5105279414768576647", "%llu", "5105279414768576647", "\x8e\xa2\xa9\xc6\x85\xa5\xcc\xd9\x8d\x01"sv),
+TestCase("%lld", "6049173436098818195", "%llu", "6049173436098818195", "\xa6\xc2\xb1\xb6\x96\xcc\xfd\xf2\xa7\x01"sv),
+TestCase("%lld", "3892265018717256260", "%llu", "3892265018717256260", "\x88\xe9\x91\xc5\xb2\x9a\x8d\x84\x6c"sv),
+TestCase("%lld", "6832059613091767623", "%llu", "6832059613091767623", "\x8e\x95\x85\xaa\xa6\x81\xad\xd0\xbd\x01"sv),
+TestCase("%lld", "810303956798710343", "%llu", "810303956798710343", "\x8e\x89\xa5\x91\xfa\xc9\xe3\xbe\x16"sv),
+TestCase("%lld", "970283311264054945", "%llu", "970283311264054945", "\xc2\xfa\x91\xb6\xfc\xe1\x91\xf7\x1a"sv),
+TestCase("%lld", "8832180626190956378", "%llu", "8832180626190956378", "\xb4\xdd\x97\x83\x82\xdf\x9a\x92\xf5\x01"sv),
+TestCase("%lld", "5816722312163363604", "%llu", "5816722312163363604", "\xa8\xac\xb0\xd9\xfc\x87\x93\xb9\xa1\x01"sv),
+TestCase("%lld", "4851344105826850048", "%llu", "4851344105826850048", "\x80\xd4\xf4\xef\xd7\xf0\xb7\xd3\x86\x01"sv),
+TestCase("%lld", "7829421709149921671", "%llu", "7829421709149921671", "\x8e\x86\xe7\xa9\xff\xe3\xd8\xa7\xd9\x01"sv),
+TestCase("%lld", "3885303859151835407", "%llu", "3885303859151835407", "\x9e\xc4\x98\x93\xca\xd1\xaf\xeb\x6b"sv),
+TestCase("%lld", "7185454812706950393", "%llu", "7185454812706950393", "\xf2\xb3\xa6\xd0\x9f\xc5\xee\xb7\xc7\x01"sv),
+TestCase("%lld", "4013414114257689954", "%llu", "4013414114257689954", "\xc4\xf5\xf2\x8d\xef\xb7\xc1\xb2\x6f"sv),
+TestCase("%lld", "964780727032512252", "%llu", "964780727032512252", "\xf8\xfb\xd3\x8e\xb5\xbd\xcb\xe3\x1a"sv),
+TestCase("%lld", "4207054084101944455", "%llu", "4207054084101944455", "\x8e\xd2\x85\x9c\xc9\xd9\xba\xe2\x74"sv),
+TestCase("%lld", "3970605724487453205", "%llu", "3970605724487453205", "\xaa\xa8\xa8\xf3\xd0\xb7\xb6\x9a\x6e"sv),
+TestCase("%lld", "7289505649862167307", "%llu", "7289505649862167307", "\x96\xac\xe6\x83\x8c\xb1\xc3\xa9\xca\x01"sv),
+TestCase("%lld", "1556249843733915123", "%llu", "1556249843733915123", "\xe6\x97\xa3\xd8\x99\xbf\xf4\x98\x2b"sv),
+TestCase("%lld", "646757550612212450", "%llu", "646757550612212450", "\xc4\xab\xd7\xc0\x99\xa4\xdf\xf9\x11"sv),
+TestCase("%lld", "551608669266414637", "%llu", "551608669266414637", "\xda\xb0\xb4\xaa\xb0\xca\xda\xa7\x0f"sv),
+TestCase("%lld", "5294527771240555016", "%llu", "5294527771240555016", "\x90\x88\xdf\xa3\x9a\xbd\xf8\xf9\x92\x01"sv),
+TestCase("%lld", "6427334826534330711", "%llu", "6427334826534330711", "\xae\x85\x9b\xc1\x94\xbe\xbd\xb2\xb2\x01"sv),
+TestCase("%lld", "4061389961487213535", "%llu", "4061389961487213535", "\xbe\x8f\xc3\xcc\xdb\xa9\xfa\xdc\x70"sv),
+TestCase("%lld", "6681126070454200740", "%llu", "6681126070454200740", "\xc8\xa6\xb1\x90\xea\xb0\x90\xb8\xb9\x01"sv),
+TestCase("%lld", "7083829078452288754", "%llu", "7083829078452288754", "\xe4\xf3\xd9\xeb\xfb\xc0\xe8\xce\xc4\x01"sv),
+TestCase("%lld", "4993075148853633222", "%llu", "4993075148853633222", "\x8c\x83\x83\x84\x97\xd9\xfb\xca\x8a\x01"sv),
+TestCase("%lld", "4002626457632111277", "%llu", "4002626457632111277", "\xda\xea\xef\xfb\xcd\xe3\x97\x8c\x6f"sv),
+TestCase("%lld", "6319581401334276901", "%llu", "6319581401334276901", "\xca\x8c\x8f\xbb\xa9\xf3\xd4\xb3\xaf\x01"sv),
+TestCase("%lld", "3705575452837642392", "%llu", "3705575452837642392", "\xb0\x82\xdc\xac\xb9\xcf\xec\xec\x66"sv),
+TestCase("%lld", "4291800171892412066", "%llu", "4291800171892412066", "\xc4\xba\xdc\xc9\x8e\xe1\xc4\x8f\x77"sv),
+TestCase("%lld", "6557245727662973594", "%llu", "6557245727662973594", "\xb4\x8a\xe5\xe4\xeb\x8f\x82\x80\xb6\x01"sv),
+TestCase("%lld", "3849893339411366329", "%llu", "3849893339411366329", "\xf2\x86\x8e\xec\x86\xe6\xc8\xed\x6a"sv),
+TestCase("%lld", "5454275499512896944", "%llu", "5454275499512896944", "\xe0\xf6\xa8\xf9\xe6\xaa\xbd\xb1\x97\x01"sv),
+TestCase("%lld", "5265826841850021261", "%llu", "5265826841850021261", "\x9a\x96\x80\xd3\x9e\xe7\xfc\x93\x92\x01"sv),
+TestCase("%lld", "9044083069173435164", "%llu", "9044083069173435164", "\xb8\xac\xa8\x93\xc8\xe8\x84\x83\xfb\x01"sv),
+TestCase("%lld", "7588739275664019217", "%llu", "7588739275664019217", "\xa2\xbc\x92\xb0\xc2\x8a\xcf\xd0\xd2\x01"sv),
+TestCase("%lld", "5076270483973408909", "%llu", "5076270483973408909", "\x9a\xa2\xc8\xd0\x84\xc7\xc4\xf2\x8c\x01"sv),
+TestCase("%lld", "4196804979465246477", "%llu", "4196804979465246477", "\x9a\xdc\xee\x89\x95\xf9\x85\xbe\x74"sv),
+TestCase("%lld", "6514086955327654755", "%llu", "6514086955327654755", "\xc6\xad\xfc\x84\xd4\xe4\xd7\xe6\xb4\x01"sv),
+TestCase("%lld", "9208944818170478756", "%llu", "9208944818170478756", "\xc8\x82\xce\xb3\xac\xa1\xdf\xcc\xff\x01"sv),
+TestCase("%lld", "4628058100229254151", "%llu", "4628058100229254151", "\x8e\xb0\xc5\x80\xcb\x94\x95\xba\x80\x01"sv),
+TestCase("%lld", "5505985599159795437", "%llu", "5505985599159795437", "\xda\xfb\xaf\x85\xe0\xae\x98\xe9\x98\x01"sv),
+TestCase("%lld", "1076793340331741575", "%llu", "1076793340331741575", "\x8e\xc6\xd6\xaf\xd0\xf5\xc4\xf1\x1d"sv),
+TestCase("%lld", "8835790874608329711", "%llu", "8835790874608329711", "\xde\x8f\xd7\xea\x90\xbf\x84\x9f\xf5\x01"sv),
+TestCase("%lld", "5264779051526567427", "%llu", "5264779051526567427", "\x86\xd8\xc5\xb2\xe8\xa9\xa0\x90\x92\x01"sv),
+TestCase("%lld", "3825505252128459194", "%llu", "3825505252128459194", "\xf4\xa6\x90\xf8\xc4\xb0\xf6\x96\x6a"sv),
+TestCase("%lld", "6197432947465793532", "%llu", "6197432947465793532", "\xf8\xbf\x81\x90\xc5\x9b\xda\x81\xac\x01"sv),
+TestCase("%lld", "6345326312201569781", "%llu", "6345326312201569781", "\xea\x97\xb6\xeb\xae\xaa\x90\x8f\xb0\x01"sv),
+TestCase("%lld", "1939578830432974807", "%llu", "1939578830432974807", "\xae\xdf\x99\x9d\xf0\xaa\xe2\xea\x35"sv),
+TestCase("%lld", "2091703771056304968", "%llu", "2091703771056304968", "\x90\xcd\xbf\x92\xeb\xdf\x9c\x87\x3a"sv),
+TestCase("%lld", "4252410814844357301", "%llu", "4252410814844357301", "\xea\xaa\x9e\x9d\xe3\xc6\xcc\x83\x76"sv),
+TestCase("%lld", "8660402439522563566", "%llu", "8660402439522563566", "\xdc\xd7\xd9\xba\xc9\x88\xf7\xaf\xf0\x01"sv),
+TestCase("%lld", "4095317955084962012", "%llu", "4095317955084962012", "\xb8\xa3\xf2\xb1\xee\xfe\xbe\xd5\x71"sv),
+TestCase("%lld", "3854481529221205087", "%llu", "3854481529221205087", "\xbe\xb1\xc6\xe8\xec\xa1\xef\xfd\x6a"sv),
+TestCase("%lld", "8433702361086030159", "%llu", "8433702361086030159", "\x9e\xc5\xbf\xde\xa3\xe7\xc3\x8a\xea\x01"sv),
+TestCase("%lld", "9155414173781516949", "%llu", "9155414173781516949", "\xaa\x8a\x97\xfd\xdf\xab\xc8\x8e\xfe\x01"sv),
+TestCase("%lld", "5127196966915280688", "%llu", "5127196966915280688", "\xe0\xbc\x90\xa7\xbe\x9e\xbb\xa7\x8e\x01"sv),
+TestCase("%lld", "4492265357832577542", "%llu", "4492265357832577542", "\x8c\x88\xbb\xa6\xd7\xe2\xdd\xd7\x7c"sv),
+TestCase("%lld", "8259453203008866922", "%llu", "8259453203008866922", "\xd4\xa9\xf2\xc4\xaf\xbb\xbc\x9f\xe5\x01"sv),
+TestCase("%lld", "5064367472631091289", "%llu", "5064367472631091289", "\xb2\xc1\xc0\xa3\xcb\xd8\x9f\xc8\x8c\x01"sv),
+TestCase("%lld", "8275937406817640763", "%llu", "8275937406817640763", "\xf6\x84\xdd\xcb\xa8\xce\x84\xda\xe5\x01"sv),
+TestCase("%lld", "1904310933731893847", "%llu", "1904310933731893847", "\xae\x89\xdb\xaf\x8d\xad\xbc\xed\x34"sv),
+TestCase("%lld", "997754361730893686", "%llu", "997754361730893686", "\xec\x9d\x88\x81\xc8\x93\xde\xd8\x1b"sv),
+TestCase("%lld", "4328168087819780921", "%llu", "4328168087819780921", "\xf2\x8c\xf0\xaa\xb7\xfc\xde\x90\x78"sv),
+TestCase("%lld", "256781249510239018", "%llu", "256781249510239018", "\xd4\xac\xcf\xa0\xf3\xcb\xa2\x90\x07"sv),
+TestCase("%lld", "7784993871819474513", "%llu", "7784993871819474513", "\xa2\x99\xa4\xc6\xc0\xab\xed\x89\xd8\x01"sv),
+TestCase("%lld", "2332871340892345868", "%llu", "2332871340892345868", "\x98\xd8\x9e\xc9\xfb\x87\x83\xe0\x40"sv),
+TestCase("%lld", "4894959346005630664", "%llu", "4894959346005630664", "\x90\xfb\xa6\x8a\xe6\xe5\xb1\xee\x87\x01"sv),
+TestCase("%lld", "1987972021100915124", "%llu", "1987972021100915124", "\xe8\xa6\x95\xd2\xa7\x81\xd9\x96\x37"sv),
+TestCase("%lld", "3114100465793448092", "%llu", "3114100465793448092", "\xb8\xe2\xf9\xa7\xfb\xf7\xc0\xb7\x56"sv),
+TestCase("%lld", "5784824274936856659", "%llu", "5784824274936856659", "\xa6\x81\xd8\xba\xeb\xc1\xe9\xc7\xa0\x01"sv),
+TestCase("%lld", "3207654208325833422", "%llu", "3207654208325833422", "\x9c\xcb\xe2\x98\xbe\xa0\xf0\x83\x59"sv),
+TestCase("%lld", "1164916386415315063", "%llu", "1164916386415315063", "\xee\xa1\x88\xd5\x81\xd2\xce\xaa\x20"sv),
+TestCase("%lld", "293049028994436992", "%llu", "293049028994436992", "\x80\xbe\xe4\xaf\xc1\xa2\x8f\x91\x08"sv),
+TestCase("%lld", "3140932642079254647", "%llu", "3140932642079254647", "\xee\xa1\x81\xa9\x97\xe6\xea\x96\x57"sv),
+TestCase("%lld", "1847278515897189565", "%llu", "1847278515897189565", "\xfa\x92\x9a\xd4\xbb\x81\xed\xa2\x33"sv),
+TestCase("%lld", "7940449326902609449", "%llu", "7940449326902609449", "\xd2\x98\xa8\xf0\xf5\xa5\x92\xb2\xdc\x01"sv),
+TestCase("%lld", "4324626723061920101", "%llu", "4324626723061920101", "\xca\xc5\xa3\xe4\xef\xc5\x94\x84\x78"sv),
+TestCase("%lld", "5240496025593009868", "%llu", "5240496025593009868", "\x98\xdb\x8a\xc5\xd6\xd7\xfd\xb9\x91\x01"sv),
+TestCase("%lld", "483285195300941883", "%llu", "483285195300941883", "\xf6\xc0\xf1\xf1\xe2\xd4\xfc\xb4\x0d"sv),
+TestCase("%lld", "5038839083535780307", "%llu", "5038839083535780307", "\xa6\xa7\x8a\xe8\xe2\xdc\xc6\xed\x8b\x01"sv),
+TestCase("%lld", "3649778670280906901", "%llu", "3649778670280906901", "\xaa\xe2\xa0\xee\x9f\x97\xcf\xa6\x65"sv),
+TestCase("%lld", "3630797309549363234", "%llu", "3630797309549363234", "\xc4\x80\x92\xf2\xd1\xba\x97\xe3\x64"sv),
+TestCase("%lld", "8062002663843236945", "%llu", "8062002663843236945", "\xa2\xb1\x88\xca\xab\xad\xfe\xe1\xdf\x01"sv),
+TestCase("%lld", "3467724524658884800", "%llu", "3467724524658884800", "\x80\x83\xa0\xf3\xa2\xc5\xea\x9f\x60"sv),
+TestCase("%lld", "5254956975956143854", "%llu", "5254956975956143854", "\xdc\xbb\x9d\xdd\xe2\xe1\xad\xed\x91\x01"sv),
+TestCase("%lld", "7712429266319135356", "%llu", "7712429266319135356", "\xf8\xc9\xba\xe4\xc6\xe3\x86\x88\xd6\x01"sv),
+TestCase("%lld", "5229125731492990833", "%llu", "5229125731492990833", "\xe2\xbd\xf4\xf7\xb9\x89\xcb\x91\x91\x01"sv),
+TestCase("%lld", "3291981441856444913", "%llu", "3291981441856444913", "\xe2\xe7\xc0\x90\xc0\xeb\xbb\xaf\x5b"sv),
+TestCase("%lld", "7968602228742132523", "%llu", "7968602228742132523", "\xd6\xcc\xfc\x88\xbe\xe0\x94\x96\xdd\x01"sv),
+TestCase("%lld", "4919847686728209916", "%llu", "4919847686728209916", "\xf8\xd7\xb2\xc7\xf2\xd9\xe7\xc6\x88\x01"sv),
+TestCase("%lld", "3071319170242969136", "%llu", "3071319170242969136", "\xe0\xf8\xef\xe2\xe8\xa0\xc2\x9f\x55"sv),
+TestCase("%lld", "6001314382948090489", "%llu", "6001314382948090489", "\xf2\x89\x82\xd1\xd1\xe8\xf9\xc8\xa6\x01"sv),
+TestCase("%lld", "5477548339997458345", "%llu", "5477548339997458345", "\xd2\xfe\xa1\xd9\xb4\xcc\x94\x84\x98\x01"sv),
+TestCase("%lld", "3119268858057174477", "%llu", "3119268858057174477", "\x9a\xb7\xfd\xb6\xfe\x9f\xef\xc9\x56"sv),
+TestCase("%lld", "2972175343159318480", "%llu", "2972175343159318480", "\xa0\xdf\xfc\xb2\xc6\xee\xa4\xbf\x52"sv),
+TestCase("%lld", "3141952960809904282", "%llu", "3141952960809904282", "\xb4\xa2\xfd\xa0\xc6\xe4\xba\x9a\x57"sv),
+TestCase("%lld", "7404358508754117308", "%llu", "7404358508754117308", "\xf8\x8a\xf1\xfe\xad\xb5\xc8\xc1\xcd\x01"sv),
+TestCase("%lld", "3962329461475475834", "%llu", "3962329461475475834", "\xf4\x85\xfc\xa4\xdc\xe9\x82\xfd\x6d"sv),
+TestCase("%lld", "8646072927884668865", "%llu", "8646072927884668865", "\x82\xbf\xff\x8a\x9c\xe1\x82\xfd\xef\x01"sv),
+TestCase("%lld", "6705183005166907218", "%llu", "6705183005166907218", "\xa4\xbd\xad\x8f\xdd\x9a\xcc\x8d\xba\x01"sv),
+TestCase("%lld", "8724568879186548965", "%llu", "8724568879186548965", "\xca\xe3\xbc\xe5\xf7\xca\xf2\x93\xf2\x01"sv),
+TestCase("%lld", "5275577307833715720", "%llu", "5275577307833715720", "\x90\xb0\xd2\xa8\x87\xe7\xce\xb6\x92\x01"sv),
+TestCase("%lld", "6278774444907314544", "%llu", "6278774444907314544", "\xe0\xf5\x81\xd8\xeb\x85\xd8\xa2\xae\x01"sv),
+TestCase("%lld", "4999233507689781945", "%llu", "4999233507689781945", "\xf2\xea\xa2\xf1\xeb\x98\xec\xe0\x8a\x01"sv),
+TestCase("%lld", "4257240410533938703", "%llu", "4257240410533938703", "\x9e\x98\x82\x95\x9f\xe6\xe0\x94\x76"sv),
+TestCase("%lld", "2788213596089450528", "%llu", "2788213596089450528", "\xc0\x80\x9c\x8d\xc7\xdf\xdc\xb1\x4d"sv),
+TestCase("%lld", "7141567845119971606", "%llu", "7141567845119971606", "\xac\xc4\xd2\xbb\xc2\x87\xf9\x9b\xc6\x01"sv),
+TestCase("%lld", "1507235571856201971", "%llu", "1507235571856201971", "\xe6\xe3\x92\xd5\x8a\xb1\xe3\xea\x29"sv),
+TestCase("%lld", "2967582888677845820", "%llu", "2967582888677845820", "\xf8\xac\xdd\x92\xc2\xba\xfc\xae\x52"sv),
+TestCase("%lld", "8120818212305491728", "%llu", "8120818212305491728", "\xa0\xdc\xee\xdf\xeb\xc8\xf8\xb2\xe1\x01"sv),
+TestCase("%lld", "8554950908976020523", "%llu", "8554950908976020523", "\xd6\xf0\xdd\x86\xd2\xa1\xa5\xb9\xed\x01"sv),
+TestCase("%lld", "850107129610567275", "%llu", "850107129610567275", "\xd6\xf9\xb7\x97\xc5\xfb\x97\xcc\x17"sv),
+TestCase("%lld", "3548380829014863866", "%llu", "3548380829014863866", "\xf4\xbf\xe5\x93\x8b\xe4\xb0\xbe\x62"sv),
+TestCase("%lld", "3619717899166040237", "%llu", "3619717899166040237", "\xda\x82\xd2\xee\x89\x90\xe9\xbb\x64"sv),
+TestCase("%lld", "25117409533303638", "%llu", "25117409533303638", "\xac\xed\xc8\x8e\xd4\x89\x9e\x59"sv),
+TestCase("%lld", "4766478454387430761", "%llu", "4766478454387430761", "\xd2\xd5\x86\xf5\xcf\xb9\xf7\xa5\x84\x01"sv),
+TestCase("%lld", "4030046377721652704", "%llu", "4030046377721652704", "\xc0\x97\x97\xc7\x82\xf5\xcc\xed\x6f"sv),
+TestCase("%lld", "7278057998974406311", "%llu", "7278057998974406311", "\xce\xba\xfa\xb8\x8e\xcc\xed\x80\xca\x01"sv),
+TestCase("%lld", "8751771536062737093", "%llu", "8751771536062737093", "\x8a\xab\xa7\xa6\xfb\xf5\xc4\xf4\xf2\x01"sv),
+TestCase("%lld", "3273441488341945355", "%llu", "3273441488341945355", "\x96\xb0\xae\x9a\x96\xec\xcc\xed\x5a"sv),
+TestCase("%lld", "5220278114967040707", "%llu", "5220278114967040707", "\x86\xcb\xd9\xf5\xb1\xd2\x93\xf2\x90\x01"sv),
+TestCase("%lld", "1352148195246416250", "%llu", "1352148195246416250", "\xf4\x85\xc9\xd5\xd3\xe7\xe5\xc3\x25"sv),
+TestCase("%lld", "5133499936442391273", "%llu", "5133499936442391273", "\xd2\xeb\x9c\xe2\xcc\xbf\xed\xbd\x8e\x01"sv),
+TestCase("%lld", "1676322249364352341", "%llu", "1676322249364352341", "\xaa\xb5\x91\xa7\x89\x8d\xbf\xc3\x2e"sv),
+TestCase("%lld", "5174306005887408729", "%llu", "5174306005887408729", "\xb2\xe9\xb6\x81\xba\xf9\xe9\xce\x8f\x01"sv),
+TestCase("%lld", "2333671424671038513", "%llu", "2333671424671038513", "\xe2\xf0\xf9\x9f\xfc\xf2\xee\xe2\x40"sv),
+TestCase("%lld", "6396005668587144321", "%llu", "6396005668587144321", "\x82\x92\xac\x98\x94\xd1\x96\xc3\xb1\x01"sv),
+TestCase("%lld", "4281900075145433102", "%llu", "4281900075145433102", "\x9c\xc0\x98\xec\xd1\xdb\xae\xec\x76"sv),
+TestCase("%lld", "8097189911969434437", "%llu", "8097189911969434437", "\x8a\xad\x84\xdd\xdf\xd4\xff\xde\xe0\x01"sv),
+TestCase("%lld", "1034989118514460270", "%llu", "1034989118514460270", "\xdc\xd9\x97\xb7\xd4\xc7\x82\xdd\x1c"sv),
+TestCase("%lld", "2879184457747193418", "%llu", "2879184457747193418", "\x94\xf9\xc6\xed\xcf\xc0\xf5\xf4\x4f"sv),
+TestCase("%lld", "2474224803315291647", "%llu", "2474224803315291647", "\xfe\xd7\x8c\x81\xb0\x96\x9b\xd6\x44"sv),
+TestCase("%lld", "3663174277129677254", "%llu", "3663174277129677254", "\x8c\xa7\xcd\xaa\x98\xe6\x9a\xd6\x65"sv),
+TestCase("%lld", "6086297047179889204", "%llu", "6086297047179889204", "\xe8\xa8\xd4\xea\xde\xba\xef\xf6\xa8\x01"sv),
+TestCase("%lld", "3403521624041286389", "%llu", "3403521624041286389", "\xea\xab\xbb\xa5\xcd\xb8\xde\xbb\x5e"sv),
+TestCase("%lld", "4129732865765686804", "%llu", "4129732865765686804", "\xa8\xf8\xc1\x93\xa8\x8a\xe1\xcf\x72"sv),
+TestCase("%lld", "8456228405596121943", "%llu", "8456228405596121943", "\xae\xad\xff\xf1\xd1\xbb\xc7\xda\xea\x01"sv),
+TestCase("%lld", "8297459921109698914", "%llu", "8297459921109698914", "\xc4\xa5\xc6\xfc\xc3\xf5\xbf\xa6\xe6\x01"sv),
+TestCase("%lld", "2758981765762549667", "%llu", "2758981765762549667", "\xc6\x9e\xf4\xa0\x8b\xd3\xef\xc9\x4c"sv),
+TestCase("%lld", "8848352886883606505", "%llu", "8848352886883606505", "\xd2\xff\xfe\xdf\xbd\x84\xd5\xcb\xf5\x01"sv),
+TestCase("%lld", "1195805645371054808", "%llu", "1195805645371054808", "\xb0\xeb\xfc\x8b\xc3\xb9\xad\x98\x21"sv),
+TestCase("%lld", "639367729021931858", "%llu", "639367729021931858", "\xa4\xc5\x9a\x93\xfc\xe3\xbe\xdf\x11"sv),
+TestCase("%lld", "2232000679497729109", "%llu", "2232000679497729109", "\xaa\x91\xa4\xfe\xda\xb2\xd4\xf9\x3d"sv),
+TestCase("%lld", "4168177859746898377", "%llu", "4168177859746898377", "\x92\x87\xe5\xad\xbe\xeb\xab\xd8\x73"sv),
+TestCase("%lld", "1260186573202255124", "%llu", "1260186573202255124", "\xa8\xb4\xff\xe6\xde\xc0\x8a\xfd\x22"sv),
+TestCase("%lld", "8933020158994285890", "%llu", "8933020158994285890", "\x84\x95\x90\xc4\xac\xa0\xbb\xf8\xf7\x01"sv),
+TestCase("%lld", "7418694076181766474", "%llu", "7418694076181766474", "\x94\x85\xdf\xc1\x9a\xbd\xbf\xf4\xcd\x01"sv),
+TestCase("%lld", "2904831533300168615", "%llu", "2904831533300168615", "\xce\xde\xad\xab\xf3\xb8\x84\xd0\x50"sv),
+TestCase("%lld", "7673041265242461202", "%llu", "7673041265242461202", "\xa4\xd0\x86\x9b\x92\x98\x8f\xfc\xd4\x01"sv),
+TestCase("%lld", "625118648746706096", "%llu", "625118648746706096", "\xe0\x92\xa8\x90\xab\x86\xef\xac\x11"sv),
+TestCase("%lld", "7640289034141395187", "%llu", "7640289034141395187", "\xe6\xa3\xfb\xd4\xb0\x99\xe1\x87\xd4\x01"sv),
+TestCase("%lld", "3317249430279903162", "%llu", "3317249430279903162", "\xf4\xde\x9a\xb3\x80\xb2\x9e\x89\x5c"sv),
+TestCase("%lld", "3172349440293538054", "%llu", "3172349440293538054", "\x8c\xa4\xd8\xc1\xc2\xc0\xb9\x86\x58"sv),
+TestCase("%lld", "2529759202900350655", "%llu", "2529759202900350655", "\xfe\xda\xb4\x82\xf0\xa5\xc1\x9b\x46"sv),
+TestCase("%lld", "7039166187898074754", "%llu", "7039166187898074754", "\x84\xda\xd7\x89\xca\x96\x92\xb0\xc3\x01"sv),
+TestCase("%lld", "4162847733300040370", "%llu", "4162847733300040370", "\xe4\xca\x9c\xd6\xa5\xfd\xb3\xc5\x73"sv),
+TestCase("%lld", "2754081621805980671", "%llu", "2754081621805980671", "\xfe\xef\xea\xe2\x95\xa9\xbb\xb8\x4c"sv),
+TestCase("%lld", "8048952107112912959", "%llu", "8048952107112912959", "\xfe\xe0\xc6\xe9\xfa\xd2\xcf\xb3\xdf\x01"sv),
+TestCase("%lld", "6063152674026149391", "%llu", "6063152674026149391", "\x9e\xd8\xce\xa4\xf5\xce\xd2\xa4\xa8\x01"sv),
+TestCase("%lld", "1860779983819210237", "%llu", "1860779983819210237", "\xfa\xf7\x9b\xcb\xaa\xe2\xe8\xd2\x33"sv),
+TestCase("%lld", "5403475014208127092", "%llu", "5403475014208127092", "\xe8\x81\x98\xe9\xb0\xf9\xff\xfc\x95\x01"sv),
+TestCase("%lld", "133586295505940516", "%llu", "133586295505940516", "\xc8\xc0\xef\xa7\xe5\x81\xcc\xda\x03"sv),
+TestCase("%lld", "5951209029639904448", "%llu", "5951209029639904448", "\x80\xf3\xc4\xbc\x9c\xc5\xf8\x96\xa5\x01"sv),
+TestCase("%lld", "476590412526583632", "%llu", "476590412526583632", "\xa0\xfd\x87\xa0\x90\x9d\x98\x9d\x0d"sv),
+TestCase("%lld", "6125277199963519765", "%llu", "6125277199963519765", "\xaa\xec\xff\xe9\xa0\xca\xad\x81\xaa\x01"sv),
+TestCase("%lld", "7182329075191030773", "%llu", "7182329075191030773", "\xea\x8f\xc5\xab\xb0\x8f\xe1\xac\xc7\x01"sv),
+TestCase("%lld", "3739073677431428339", "%llu", "3739073677431428339", "\xe6\xd3\x82\xc5\xdf\xec\xed\xe3\x67"sv),
+TestCase("%lld", "6781911602451840052", "%llu", "6781911602451840052", "\xe8\xf0\xef\xa3\xf3\xaa\x98\x9e\xbc\x01"sv),
+TestCase("%lld", "5097852782557067317", "%llu", "5097852782557067317", "\xea\xe0\xe7\xd8\x93\x86\x9b\xbf\x8d\x01"sv),
+TestCase("%lld", "2755779697069684711", "%llu", "2755779697069684711", "\xce\xdf\x93\xb1\x94\xc2\xbf\xbe\x4c"sv),
+TestCase("%lld", "4587140119625129692", "%llu", "4587140119625129692", "\xb8\xfb\x96\xd3\xd2\xe8\xe5\xa8\x7f"sv),
+TestCase("%lld", "7605101363071432517", "%llu", "7605101363071432517", "\x8a\xdd\xb1\xab\xad\xd9\xdf\x8a\xd3\x01"sv),
+TestCase("%lld", "785644280652604147", "%llu", "785644280652604147", "\xe6\x8b\xed\xc1\xf1\xd3\x95\xe7\x15"sv),
+TestCase("%lld", "8440954818933111077", "%llu", "8440954818933111077", "\xca\xe4\x89\xba\xf1\xeb\xa5\xa4\xea\x01"sv),
+TestCase("%lld", "3478923299848040400", "%llu", "3478923299848040400", "\xa0\xbf\xad\x84\xe4\x93\xcf\xc7\x60"sv),
+TestCase("%lld", "177390365453428882", "%llu", "177390365453428882", "\xa4\xa2\xed\xf6\x9e\xe6\x9b\xf6\x04"sv),
+TestCase("%lld", "2241679290640917987", "%llu", "2241679290640917987", "\xc6\x97\x83\xb0\x83\xdc\x85\x9c\x3e"sv),
+TestCase("%lld", "2690515256289614444", "%llu", "2690515256289614444", "\xd8\xe9\xca\x9e\xdc\xd7\xd0\xd6\x4a"sv),
+TestCase("%lld", "5284047940288483819", "%llu", "5284047940288483819", "\xd6\xc7\xbc\x90\xe1\xe6\xda\xd4\x92\x01"sv),
+TestCase("%lld", "7345613454774364586", "%llu", "7345613454774364586", "\xd4\xa6\xbf\xd2\x96\xa1\xee\xf0\xcb\x01"sv),
+TestCase("%lld", "7027616933390126935", "%llu", "7027616933390126935", "\xae\xbd\xe8\xc3\xbe\x97\x8e\x87\xc3\x01"sv),
+TestCase("%lld", "6370586723714568954", "%llu", "6370586723714568954", "\xf4\xcb\x9c\xdd\xea\xb7\xef\xe8\xb0\x01"sv),
+TestCase("%lld", "5815295050302678849", "%llu", "5815295050302678849", "\x82\x9d\x87\x96\xb3\x82\x8a\xb4\xa1\x01"sv),
+TestCase("%lld", "731720363738568228", "%llu", "731720363738568228", "\xc8\xd8\xf4\xb8\xe8\xf2\xcb\xa7\x14"sv),
+TestCase("%lld", "7895064303389261530", "%llu", "7895064303389261530", "\xb4\xfb\x91\xa6\xee\xc9\xf3\x90\xdb\x01"sv),
+TestCase("%lld", "9161537274294991160", "%llu", "9161537274294991160", "\xf0\xe4\xd3\xed\x8d\xe7\xa8\xa4\xfe\x01"sv),
+TestCase("%lld", "5626051873332343381", "%llu", "5626051873332343381", "\xaa\xc9\xa9\xde\xdc\x97\xe0\x93\x9c\x01"sv),
+TestCase("%lld", "1558715097695343451", "%llu", "1558715097695343451", "\xb6\xcd\xda\x82\xef\xc7\xd5\xa1\x2b"sv),
+TestCase("%lld", "418389641884993571", "%llu", "418389641884993571", "\xc6\xa0\x90\xe4\xb3\xca\xb5\xce\x0b"sv),
+TestCase("%lld", "366808135709449019", "%llu", "366808135709449019", "\xf6\xec\xfa\xda\xca\x83\x95\x97\x0a"sv),
+TestCase("%lld", "7559429297343079575", "%llu", "7559429297343079575", "\xae\x92\x85\xcd\x9e\xb9\xbe\xe8\xd1\x01"sv),
+TestCase("%lld", "891656539731055874", "%llu", "891656539731055874", "\x84\xd4\x96\x85\xc2\xb9\xe6\xdf\x18"sv),
+TestCase("%lld", "3187530423573642791", "%llu", "3187530423573642791", "\xce\xf8\xdb\xfd\x85\x82\xb1\xbc\x58"sv),
+TestCase("%lld", "3732114429930137548", "%llu", "3732114429930137548", "\x98\x9f\xc3\x91\x9d\x93\x91\xcb\x67"sv),
+TestCase("%lld", "4317425874089886561", "%llu", "4317425874089886561", "\xc2\x8d\xaa\x93\xa6\xfd\xc9\xea\x77"sv),
+TestCase("%lld", "8113837181842537067", "%llu", "8113837181842537067", "\xd6\xe9\xa5\xd1\xb1\xfb\x91\x9a\xe1\x01"sv),
+TestCase("%lld", "4647576880061632962", "%llu", "4647576880061632962", "\x84\xc7\xa2\xa6\x8d\xa3\xc1\xff\x80\x01"sv),
+TestCase("%lld", "2770024080011047550", "%llu", "2770024080011047550", "\xfc\xc9\xb1\xc5\xaf\x8e\x8d\xf1\x4c"sv),
+TestCase("%lld", "5715833275239865339", "%llu", "5715833275239865339", "\xf6\xef\xbc\xe0\x8f\x85\xdc\xd2\x9e\x01"sv),
+TestCase("%lld", "4697198541546045125", "%llu", "4697198541546045125", "\x8a\xfb\xad\xc5\xf9\xcb\xe6\xaf\x82\x01"sv),
+TestCase("%lld", "9140962295727161515", "%llu", "9140962295727161515", "\xd6\x82\xf4\xe0\xdd\xb1\x9c\xdb\xfd\x01"sv),
+TestCase("%lld", "1018100739973888361", "%llu", "1018100739973888361", "\xd2\xe5\xa5\xfb\xd2\xce\x82\xa1\x1c"sv),
+TestCase("%lld", "2450943428072886064", "%llu", "2450943428072886064", "\xe0\xbc\xe6\xad\xfd\x83\xc0\x83\x44"sv),
+TestCase("%lld", "4859753067646279323", "%llu", "4859753067646279323", "\xb6\xfa\xde\xd2\xd7\xea\xa7\xf1\x86\x01"sv),
+TestCase("%lld", "2904532547632894569", "%llu", "2904532547632894569", "\xd2\xc9\xc0\xc3\xd2\xbd\xfc\xce\x50"sv),
+TestCase("%lld", "549670438443165922", "%llu", "549670438443165922", "\xc4\xa3\x81\xaf\xbf\x96\xe9\xa0\x0f"sv),
+TestCase("%lld", "569815776002665926", "%llu", "569815776002665926", "\x8c\xb7\xeb\xc0\xbc\x9b\xb2\xe8\x0f"sv),
+TestCase("%lld", "3444356383434812300", "%llu", "3444356383434812300", "\x98\xae\xd9\xa7\xb7\xf8\xe7\xcc\x5f"sv),
+TestCase("%lld", "2151972984861854191", "%llu", "2151972984861854191", "\xde\xa7\xec\x8e\xe6\x81\xac\xdd\x3b"sv),
+TestCase("%lld", "2011546512680833085", "%llu", "2011546512680833085", "\xfa\xc0\xa2\x98\xa9\xb9\xb9\xea\x37"sv),
+TestCase("%lld", "4628394148228357534", "%llu", "4628394148228357534", "\xbc\xf6\xae\xda\x93\xfd\xad\xbb\x80\x01"sv),
+TestCase("%lld", "4692262078416922470", "%llu", "4692262078416922470", "\xcc\x9d\x9d\x91\xfd\xdf\xa1\x9e\x82\x01"sv),
+TestCase("%lld", "7858502056959645817", "%llu", "7858502056959645817", "\xf2\xe1\x8c\xbc\x82\xff\x80\x8f\xda\x01"sv),
+TestCase("%lld", "4919691286333696629", "%llu", "4919691286333696629", "\xea\xa9\x89\xe8\x98\xca\xa0\xc6\x88\x01"sv),
+TestCase("%lld", "5678520073874798535", "%llu", "5678520073874798535", "\x8e\xdf\xdb\xd3\xf9\xfa\x93\xce\x9d\x01"sv),
+TestCase("%lld", "1241746909443220722", "%llu", "1241746909443220722", "\xe4\x93\xeb\xe4\x85\x8f\xc9\xbb\x22"sv),
+TestCase("%lld", "5292137023426454572", "%llu", "5292137023426454572", "\xd8\x90\xb7\x80\xaf\xa5\xb9\xf1\x92\x01"sv),
+TestCase("%lld", "1941514050836526867", "%llu", "1941514050836526867", "\xa6\xdc\xd3\xe8\xc3\xaf\xd2\xf1\x35"sv),
+TestCase("%lld", "9145606885382902500", "%llu", "9145606885382902500", "\xc8\xcb\xea\xa9\xb7\xc0\xdc\xeb\xfd\x01"sv),
+TestCase("%lld", "3509957387132194637", "%llu", "3509957387132194637", "\x9a\xbd\xae\xcc\xb4\xe9\xef\xb5\x61"sv),
+TestCase("%lld", "8233948220616670992", "%llu", "8233948220616670992", "\xa0\xfc\x87\xcd\x80\x92\xee\xc4\xe4\x01"sv),
+TestCase("%lld", "8024983545482782384", "%llu", "8024983545482782384", "\xe0\x9a\xd9\xcd\x86\x81\xbc\xde\xde\x01"sv),
+TestCase("%lld", "6571000011597423770", "%llu", "6571000011597423770", "\xb4\x92\xd1\xe5\xc4\xec\xf0\xb0\xb6\x01"sv),
+TestCase("%lld", "5058360797369425151", "%llu", "5058360797369425151", "\xfe\xf3\xba\x8f\x89\x96\xf4\xb2\x8c\x01"sv),
+TestCase("%lld", "4606483085449113835", "%llu", "4606483085449113835", "\xd6\xc3\xd1\xc0\xb8\xfd\xc1\xed\x7f"sv),
+TestCase("%lld", "6529118911438932678", "%llu", "6529118911438932678", "\x8c\xab\xd2\xc9\xd4\xc3\x8b\x9c\xb5\x01"sv),
+TestCase("%lld", "4203154114144238442", "%llu", "4203154114144238442", "\xd4\xed\xcf\xdc\xb8\x99\xcd\xd4\x74"sv),
+TestCase("%lld", "4463425652718897265", "%llu", "4463425652718897265", "\xe2\xa1\xe8\xfa\xf3\xfe\xa2\xf1\x7b"sv),
+TestCase("%lld", "75152304545876858", "%llu", "75152304545876858", "\xf4\xad\x92\x88\xee\xa7\xff\x8a\x02"sv),
+TestCase("%lld", "5434900075521908661", "%llu", "5434900075521908661", "\xea\xfe\xd0\xf3\xd8\xb4\xd2\xec\x96\x01"sv),
+TestCase("%lld", "5308383100912368033", "%llu", "5308383100912368033", "\xc2\xe6\x8b\xd9\xc4\x93\x95\xab\x93\x01"sv),
+TestCase("%lld", "715267470600062067", "%llu", "715267470600062067", "\xe6\x91\x92\x88\xb2\xfe\x91\xed\x13"sv),
+TestCase("%lld", "5171700275229723650", "%llu", "5171700275229723650", "\x84\xb0\xdc\xdd\xfb\xff\xc8\xc5\x8f\x01"sv),
+TestCase("%lld", "3926011308434788247", "%llu", "3926011308434788247", "\xae\xbe\xe0\xf6\xfd\x9e\xff\xfb\x6c"sv),
+TestCase("%lld", "5440336360732713747", "%llu", "5440336360732713747", "\xa6\x8c\xeb\x81\x92\xc6\xfa\xff\x96\x01"sv),
+TestCase("%lld", "1478056893002169192", "%llu", "1478056893002169192", "\xd0\xed\xb1\x93\xb8\xba\x8e\x83\x29"sv),
+TestCase("%lld", "3160377536621687761", "%llu", "3160377536621687761", "\xa2\xcf\xad\xa7\x81\xa8\xf5\xdb\x57"sv),
+TestCase("%lld", "7128491551588143718", "%llu", "7128491551588143718", "\xcc\xc9\xc6\xcc\x87\xd3\xbe\xed\xc5\x01"sv),
+TestCase("%lld", "7376169372313052441", "%llu", "7376169372313052441", "\xb2\x94\xaf\xfd\xd4\xbd\xb5\xdd\xcc\x01"sv),
+TestCase("%lld", "193231861624509923", "%llu", "193231861624509923", "\xc6\xd7\xd4\xf6\xd6\xd6\xbf\xae\x05"sv),
+TestCase("%lld", "1955429981000213095", "%llu", "1955429981000213095", "\xce\x89\xd5\x8a\xa3\xcd\x8a\xa3\x36"sv),
+TestCase("%lld", "4888125773563526975", "%llu", "4888125773563526975", "\xfe\xfc\xfb\xea\xc3\x9f\x8e\xd6\x87\x01"sv),
+TestCase("%lld", "5921662978695331131", "%llu", "5921662978695331131", "\xf6\xf4\xbb\xcb\xda\xc6\xfc\xad\xa4\x01"sv),
+TestCase("%lld", "7673779374634366645", "%llu", "7673779374634366645", "\xea\xda\xe7\xdc\xe1\xeb\xde\xfe\xd4\x01"sv),
+TestCase("%lld", "48609148367913892", "%llu", "48609148367913892", "\xc8\x8e\xee\xd4\xe9\xf0\xd8\xac\x01"sv),
+TestCase("%lld", "5854341954344571365", "%llu", "5854341954344571365", "\xca\xc7\xfe\xe5\xac\xbf\xe6\xbe\xa2\x01"sv),
+TestCase("%lld", "4673618806014082273", "%llu", "4673618806014082273", "\xc2\xf3\xef\xc0\xd9\xe2\x83\xdc\x81\x01"sv),
+TestCase("%lld", "2326184191199164007", "%llu", "2326184191199164007", "\xce\xd9\xf1\xe8\xcf\x8c\xa2\xc8\x40"sv),
+TestCase("%lld", "3495015264127128735", "%llu", "3495015264127128735", "\xbe\xd2\xec\xed\xb0\xf7\xe4\x80\x61"sv),
+TestCase("%lld", "1424330851786829530", "%llu", "1424330851786829530", "\xb4\xbb\x91\xf6\x9f\xd7\x9e\xc4\x27"sv),
+TestCase("%lld", "5191701707746974065", "%llu", "5191701707746974065", "\xe2\xe5\xe0\xe0\xc8\xcc\xd0\x8c\x90\x01"sv),
+TestCase("%lld", "6389735809259880362", "%llu", "6389735809259880362", "\xd4\xfe\xb9\xf8\xa7\xb7\xf3\xac\xb1\x01"sv),
+TestCase("%lld", "7792983928750025393", "%llu", "7792983928750025393", "\xe2\xaa\xc9\xd7\x83\xe6\x9e\xa6\xd8\x01"sv),
+TestCase("%lld", "4564825427452770815", "%llu", "4564825427452770815", "\xfe\xe7\xf1\x8f\xce\xa2\xc2\xd9\x7e"sv),
+TestCase("%lld", "943049921933903574", "%llu", "943049921933903574", "\xac\x9b\xa4\xa5\x8a\xba\xb1\x96\x1a"sv),
+TestCase("%lld", "1621021906259841134", "%llu", "1621021906259841134", "\xdc\xc1\xf4\xfb\xb9\xb5\x83\xff\x2c"sv),
+TestCase("%lld", "7630179381739069820", "%llu", "7630179381739069820", "\xf8\x85\x89\xd1\x94\xee\xeb\xe3\xd3\x01"sv),
+TestCase("%lld", "8025284571884195522", "%llu", "8025284571884195522", "\x84\xbb\xa4\x89\x8c\xf3\xc4\xdf\xde\x01"sv),
+TestCase("%lld", "5839395391962199967", "%llu", "5839395391962199967", "\xbe\xde\xad\x8f\xf5\xca\xd9\x89\xa2\x01"sv),
+TestCase("%lld", "1310759272316304459", "%llu", "1310759272316304459", "\x96\xf1\xb3\xc7\xa1\xa7\xe0\xb0\x24"sv),
+TestCase("%lld", "3663531610526815443", "%llu", "3663531610526815443", "\xa6\xd3\xec\xcd\xdd\xa5\xbd\xd7\x65"sv),
+TestCase("%lld", "3661335273992329249", "%llu", "3661335273992329249", "\xc2\xe0\xdc\x9e\x8f\xc2\xd6\xcf\x65"sv),
+TestCase("%lld", "3545512604455022758", "%llu", "3545512604455022758", "\xcc\xc2\xb1\xf3\xb8\xbb\x98\xb4\x62"sv),
+TestCase("%lld", "4908269103466540013", "%llu", "4908269103466540013", "\xda\xaf\xaf\xe2\xd2\xaf\xd6\x9d\x88\x01"sv),
+TestCase("%lld", "9012690640501821443", "%llu", "9012690640501821443", "\x86\xc0\xca\x99\xdc\x98\xc1\x93\xfa\x01"sv),
+TestCase("%lld", "6541851858139706195", "%llu", "6541851858139706195", "\xa6\xcd\xab\x9a\xda\xe6\xa9\xc9\xb5\x01"sv),
+TestCase("%lld", "6391701285149964727", "%llu", "6391701285149964727", "\xee\xe6\xab\xbc\x88\x9d\xf1\xb3\xb1\x01"sv),
+TestCase("%lld", "4510904210489015127", "%llu", "4510904210489015127", "\xae\xed\x97\xa0\xd9\xde\xf9\x99\x7d"sv),
+TestCase("%lld", "5119199357584793664", "%llu", "5119199357584793664", "\x80\x81\xff\xb3\xad\xac\x86\x8b\x8e\x01"sv),
+TestCase("%lld", "1751202991906984173", "%llu", "1751202991906984173", "\xda\xc3\xbc\xe3\xf8\xf5\xc2\xcd\x30"sv),
+TestCase("%lld", "4471970899266236465", "%llu", "4471970899266236465", "\xe2\xc0\xaf\x8a\xdc\xf5\xd0\x8f\x7c"sv),
+TestCase("%lld", "7912743439818658557", "%llu", "7912743439818658557", "\xfa\xcb\x8c\x93\x85\x8f\xdb\xcf\xdb\x01"sv),
+TestCase("%lld", "6642713590355959471", "%llu", "6642713590355959471", "\xde\xfa\x9c\x9d\x9b\xb4\xd4\xaf\xb8\x01"sv),
+TestCase("%lld", "7865046769076850396", "%llu", "7865046769076850396", "\xb8\xbb\xc6\xca\xb3\x97\xa1\xa6\xda\x01"sv),
+TestCase("%lld", "4109397703148639581", "%llu", "4109397703148639581", "\xba\xd5\xd8\x8f\x88\xdc\xc1\x87\x72"sv),
+TestCase("%lld", "436719658465345699", "%llu", "436719658465345699", "\xc6\x82\x8e\xd4\xe5\x8d\xc5\x8f\x0c"sv),
+TestCase("%lld", "2553774266074455395", "%llu", "2553774266074455395", "\xc6\xf5\xb6\xde\xc3\x8a\xea\xf0\x46"sv),
+TestCase("%lld", "6212224561138197241", "%llu", "6212224561138197241", "\xf2\x8b\xcb\xf9\xe2\xd4\xa0\xb6\xac\x01"sv),
+TestCase("%lld", "53039180701654763", "%llu", "53039180701654763", "\xd6\xfb\xb2\xce\xd2\xb6\xb7\xbc\x01"sv),
+TestCase("%lld", "5419660422277913790", "%llu", "5419660422277913790", "\xfc\xf2\xf2\x91\x90\x9c\xc0\xb6\x96\x01"sv),
+TestCase("%lld", "7637231844465361135", "%llu", "7637231844465361135", "\xde\x83\xc6\xca\xc2\xf9\xf2\xfc\xd3\x01"sv),
+TestCase("%lld", "1955771002711010609", "%llu", "1955771002711010609", "\xe2\xb4\xcb\xe9\xac\xd7\xa5\xa4\x36"sv),
+TestCase("%lld", "7552146003088329848", "%llu", "7552146003088329848", "\xf0\xb1\xba\xdf\xdb\xb1\xce\xce\xd1\x01"sv),
+TestCase("%lld", "4550116504720438569", "%llu", "4550116504720438569", "\xd2\xd4\x87\xe8\xcf\xb6\xa1\xa5\x7e"sv),
+TestCase("%lld", "757246289038783544", "%llu", "757246289038783544", "\xf0\xd0\x9d\x9a\x9c\xdf\xa3\x82\x15"sv),
+TestCase("%lld", "4684454901623556905", "%llu", "4684454901623556905", "\xd2\xac\xe0\x99\xbd\xba\xc3\x82\x82\x01"sv),
+TestCase("%lld", "2100956932097049429", "%llu", "2100956932097049429", "\xaa\x8d\xd0\xa0\xd9\xcc\x8c\xa8\x3a"sv),
+TestCase("%lld", "6234369280162760629", "%llu", "6234369280162760629", "\xea\x9e\xac\xeb\x88\xf5\xf6\x84\xad\x01"sv),
+TestCase("%lld", "1690133901535631706", "%llu", "1690133901535631706", "\xb4\xe5\x82\xfe\x84\xf5\xc7\xf4\x2e"sv),
+TestCase("%lld", "8944940508995398022", "%llu", "8944940508995398022", "\x8c\xa6\xe1\xfa\x84\x80\xe8\xa2\xf8\x01"sv),
+TestCase("%lld", "5911305799563392167", "%llu", "5911305799563392167", "\xce\xd2\xf5\xda\xc4\xd3\x96\x89\xa4\x01"sv),
+TestCase("%lld", "4077388965675931531", "%llu", "4077388965675931531", "\x96\xde\xb0\xe3\xa9\xea\xe5\x95\x71"sv),
+TestCase("%lld", "1245196532623960857", "%llu", "1245196532623960857", "\xb2\xac\xcb\xa9\xc5\xe9\xe9\xc7\x22"sv),
+TestCase("%lld", "8856210144434134086", "%llu", "8856210144434134086", "\x8c\xf1\xa8\xb3\x88\x8d\xca\xe7\xf5\x01"sv),
+TestCase("%lld", "8547254512181346310", "%llu", "8547254512181346310", "\x8c\x80\xe4\xb6\xb1\xac\xf9\x9d\xed\x01"sv),
+TestCase("%lld", "6269182192420305924", "%llu", "6269182192420305924", "\x88\xf0\xd5\xb6\xa1\xff\xcd\x80\xae\x01"sv),
+TestCase("%lld", "6831000756220180063", "%llu", "6831000756220180063", "\xbe\xd9\xed\xee\xdb\xbf\xcb\xcc\xbd\x01"sv),
+TestCase("%lld", "3168030316008523010", "%llu", "3168030316008523010", "\x84\x94\xda\xf5\xb2\xb2\x8d\xf7\x57"sv),
+TestCase("%lld", "2407920475746907080", "%llu", "2407920475746907080", "\x90\xcf\xc5\xc4\xc7\xba\xd3\xea\x42"sv),
+TestCase("%lld", "7828361129715530032", "%llu", "7828361129715530032", "\xe0\xe4\xdb\xe2\x92\xbe\xf6\xa3\xd9\x01"sv),
+TestCase("%lld", "5162541696057507903", "%llu", "5162541696057507903", "\xfe\x80\xb4\xba\xbf\x94\x84\xa5\x8f\x01"sv),
+TestCase("%lld", "4684430121285811332", "%llu", "4684430121285811332", "\x88\xf2\x8b\xa6\x89\x98\xb8\x82\x82\x01"sv),
+TestCase("%lld", "7069327273055818092", "%llu", "7069327273055818092", "\xd8\xc5\xc8\xf7\xe5\xec\xa5\x9b\xc4\x01"sv),
+TestCase("%lld", "1094908319066476988", "%llu", "1094908319066476988", "\xf8\xc6\x9e\xb9\x95\xd4\xf2\xb1\x1e"sv),
+TestCase("%lld", "8623611379126599086", "%llu", "8623611379126599086", "\xdc\xf6\xdb\xdd\x80\xb7\x9c\xad\xef\x01"sv),
+TestCase("%lld", "4188361875415362989", "%llu", "4188361875415362989", "\xda\xb6\xdf\xee\xe9\xbb\x86\xa0\x74"sv),
+TestCase("%lld", "8306128555371214226", "%llu", "8306128555371214226", "\xa4\xd6\x89\x9a\xba\xfa\xa5\xc5\xe6\x01"sv),
+TestCase("%lld", "6350883509347762576", "%llu", "6350883509347762576", "\xa0\x86\xfa\x88\xe8\xb9\xef\xa2\xb0\x01"sv),
+TestCase("%lld", "6330969482079692999", "%llu", "6330969482079692999", "\x8e\xa3\x97\xf3\xee\xcc\x8f\xdc\xaf\x01"sv),
+TestCase("%lld", "3843711385867305612", "%llu", "3843711385867305612", "\x98\x8a\xf6\xdb\xff\xc8\xcd\xd7\x6a"sv),
+TestCase("%lld", "7659651832434418091", "%llu", "7659651832434418091", "\xd6\x86\xe3\xf6\xc9\xb0\xc6\xcc\xd4\x01"sv),
+TestCase("%lld", "5535789636778214509", "%llu", "5535789636778214509", "\xda\x91\xa2\xb7\x88\xd6\x89\xd3\x99\x01"sv),
+TestCase("%lld", "5087759841087488516", "%llu", "5087759841087488516", "\x88\x88\xa4\xe8\xd1\xa7\xad\x9b\x8d\x01"sv),
+TestCase("%lld", "8891437578007474517", "%llu", "8891437578007474517", "\xaa\xc5\x8f\xbb\xc9\xd7\xdd\xe4\xf6\x01"sv),
+TestCase("%lld", "728151566226426022", "%llu", "728151566226426022", "\xcc\xa2\x90\xf9\xba\xff\xf4\x9a\x14"sv),
+TestCase("%lld", "2483479174720276235", "%llu", "2483479174720276235", "\x96\x9c\xc2\x84\xd8\xc9\x8b\xf7\x44"sv),
+TestCase("%lld", "6359340322607236594", "%llu", "6359340322607236594", "\xe4\x97\x94\xed\x90\x95\xf5\xc0\xb0\x01"sv),
+TestCase("%lld", "6416832625845083020", "%llu", "6416832625845083020", "\x98\xee\x90\xea\xcf\xd1\x95\x8d\xb2\x01"sv),
+TestCase("%lld", "5711184686518624115", "%llu", "5711184686518624115", "\xe6\xfd\xbe\xe9\xd2\x8d\x9a\xc2\x9e\x01"sv),
+TestCase("%lld", "3612059979445064775", "%llu", "3612059979445064775", "\x8e\xd1\xd2\xe3\xbd\xda\xce\xa0\x64"sv),
+TestCase("%lld", "8992370046494348158", "%llu", "8992370046494348158", "\xfc\xcd\xc4\xe2\xbc\xba\xa8\xcb\xf9\x01"sv),
+TestCase("%lld", "8599514281591661754", "%llu", "8599514281591661754", "\xf4\x92\xdb\xb2\xa9\xab\xce\xd7\xee\x01"sv),
+TestCase("%lld", "63057463895113152", "%llu", "63057463895113152", "\x80\xf7\xbd\xfe\xbc\x9b\x83\xe0\x01"sv),
+TestCase("%lld", "1367741239369008397", "%llu", "1367741239369008397", "\x9a\x94\xf0\xd3\xa3\xda\x98\xfb\x25"sv),
+TestCase("%lld", "22816942625156124", "%llu", "22816942625156124", "\xb8\xb0\xc3\x80\xee\xf8\x87\x51"sv),
+TestCase("%lld", "5473904087316166353", "%llu", "5473904087316166353", "\xa2\xdb\xcf\xb0\xfe\xb0\x9b\xf7\x97\x01"sv),
+TestCase("%lld", "2176574837538100650", "%llu", "2176574837538100650", "\xd4\xd6\x98\xa8\x8c\xd2\xdf\xb4\x3c"sv),
+TestCase("%lld", "1822527202856628083", "%llu", "1822527202856628083", "\xe6\xbd\x8b\xfc\xb6\xb5\xf5\xca\x32"sv),
+TestCase("%lld", "6704455477950302825", "%llu", "6704455477950302825", "\xd2\xa9\xb2\xfa\x88\xaf\x81\x8b\xba\x01"sv),
+TestCase("%lld", "3032341809633898873", "%llu", "3032341809633898873", "\xf2\xc5\xd9\x8c\xea\xb3\x85\x95\x54"sv),
+TestCase("%lld", "4392190674790178652", "%llu", "4392190674790178652", "\xb8\xfd\xb6\xca\xbb\x89\x99\xf4\x79"sv),
+TestCase("%lld", "6856757614633255152", "%llu", "6856757614633255152", "\xe0\x83\xdc\xb2\x99\xae\x8c\xa8\xbe\x01"sv),
+TestCase("%lld", "353529678637100035", "%llu", "353529678637100035", "\x86\xb0\xc7\x8b\xd4\xd7\xfe\xe7\x09"sv),
+TestCase("%lld", "3224702228554841730", "%llu", "3224702228554841730", "\x84\xfa\xdf\xf6\xee\xe5\xb8\xc0\x59"sv),
+TestCase("%lld", "8122609781749075974", "%llu", "8122609781749075974", "\x8c\xc0\xa2\xa4\xf4\xa3\xa7\xb9\xe1\x01"sv),
+TestCase("%lld", "3988658283410987938", "%llu", "3988658283410987938", "\xc4\xee\x8c\x92\xee\xe4\xc7\xda\x6e"sv),
+TestCase("%lld", "7588966234436262844", "%llu", "7588966234436262844", "\xf8\xee\x8e\x80\xa1\xa5\xb6\xd1\xd2\x01"sv),
+TestCase("%lld", "72920210115635164", "%llu", "72920210115635164", "\xb8\x9f\x9d\xce\xee\xa2\x88\x83\x02"sv),
+TestCase("%lld", "8168752409782378632", "%llu", "8168752409782378632", "\x90\x82\x8f\xb3\xad\xc2\x9e\xdd\xe2\x01"sv),
+TestCase("%lld", "4655403017017936000", "%llu", "4655403017017936000", "\x80\xa2\x96\xba\x9d\x98\xa8\x9b\x81\x01"sv),
+TestCase("%lld", "8183602528750449920", "%llu", "8183602528750449920", "\x80\xd4\xc5\xd2\x85\xc9\xff\x91\xe3\x01"sv),
+TestCase("%lld", "288952216557681443", "%llu", "288952216557681443", "\xc6\xec\xdb\x8f\xd2\xa0\xc8\x82\x08"sv),
+TestCase("%lld", "1676746537447019293", "%llu", "1676746537447019293", "\xba\xcc\xaa\xee\xf1\x85\x80\xc5\x2e"sv),
+TestCase("%lld", "3219359012813218523", "%llu", "3219359012813218523", "\xb6\x8b\xf9\xb2\xe3\xfd\xba\xad\x59"sv),
+TestCase("%lld", "9123809425872405130", "%llu", "9123809425872405130", "\x94\x8a\xe2\xb7\xa6\x95\xa4\x9e\xfd\x01"sv),
+TestCase("%lld", "7496923004744506260", "%llu", "7496923004744506260", "\xa8\xee\x80\xe4\x93\xf0\xb5\x8a\xd0\x01"sv),
+TestCase("%lld", "2810327810830374172", "%llu", "2810327810830374172", "\xb8\xd4\x90\xaf\xa2\x90\xa5\x80\x4e"sv),
+TestCase("%lld", "5886875793686089828", "%llu", "5886875793686089828", "\xc8\xc1\x8a\xf8\x84\x96\xb1\xb2\xa3\x01"sv),
+TestCase("%lld", "1043235540762466020", "%llu", "1043235540762466020", "\xc8\xdb\xac\x83\xce\xcc\xa8\xfa\x1c"sv),
+TestCase("%lld", "8352418169916257811", "%llu", "8352418169916257811", "\xa6\xd8\x83\x96\xd2\x84\xe0\xe9\xe7\x01"sv),
+TestCase("%lld", "674709631558538790", "%llu", "674709631558538790", "\xcc\xd8\xa6\xb9\xb9\xb5\x86\xdd\x12"sv),
+TestCase("%lld", "6425607598961991251", "%llu", "6425607598961991251", "\xa6\xf9\xf0\xa2\xa4\x84\xac\xac\xb2\x01"sv),
+TestCase("%lld", "6117996971473322038", "%llu", "6117996971473322038", "\xec\x80\xaa\xd9\x97\xf5\xbe\xe7\xa9\x01"sv),
+TestCase("%lld", "2394571431170796410", "%llu", "2394571431170796410", "\xf4\xad\xdf\x83\xf3\x81\x9d\xbb\x42"sv),
+TestCase("%lld", "9192320418779887179", "%llu", "9192320418779887179", "\x96\xd9\x8f\xef\xf8\xad\xd7\x91\xff\x01"sv),
+TestCase("%lld", "891164515808164858", "%llu", "891164515808164858", "\xf4\xef\xed\x92\xfa\xd9\x86\xde\x18"sv),
+TestCase("%lld", "4569690489566792107", "%llu", "4569690489566792107", "\xd6\xa6\xe0\xc1\xbf\xd2\xe6\xea\x7e"sv),
+TestCase("%lld", "4245075167591901055", "%llu", "4245075167591901055", "\xfe\x9d\xad\x92\xf4\xd7\xc4\xe9\x75"sv),
+TestCase("%lld", "9019812505721196330", "%llu", "9019812505721196330", "\xd4\xbc\x9a\x82\xeb\xeb\xe7\xac\xfa\x01"sv),
+TestCase("%lld", "979687222629424543", "%llu", "979687222629424543", "\xbe\xf6\x86\xfd\xd3\x95\xc6\x98\x1b"sv),
+TestCase("%lld", "4114720885129059820", "%llu", "4114720885129059820", "\xd8\xf7\xfc\xbf\x84\xb6\xb6\x9a\x72"sv),
+TestCase("%lld", "2602113737417537288", "%llu", "2602113737417537288", "\x90\xdc\xfd\xfe\x8a\xaa\xc8\x9c\x48"sv),
+TestCase("%lld", "4025468271985009702", "%llu", "4025468271985009702", "\xcc\x90\xad\xe4\x98\x84\xab\xdd\x6f"sv),
+TestCase("%lld", "3036291044299697402", "%llu", "3036291044299697402", "\xf4\xb3\xf7\xa2\xc5\xa7\x89\xa3\x54"sv),
+TestCase("%lld", "1070534622568918680", "%llu", "1070534622568918680", "\xb0\xba\xd1\x9f\xa7\xe4\xa6\xdb\x1d"sv),
+TestCase("%lld", "5631405388638357750", "%llu", "5631405388638357750", "\xec\xa3\x94\x80\xaa\xd7\xe2\xa6\x9c\x01"sv),
+TestCase("%lld", "53610470084017162", "%llu", "53610470084017162", "\x94\xf0\xcb\x99\x88\x9c\xbb\xbe\x01"sv),
+TestCase("%lld", "6477169593541844553", "%llu", "6477169593541844553", "\x92\xf9\xae\xd0\xb0\xdb\xc3\xe3\xb3\x01"sv),
+TestCase("%lld", "1775599305804553124", "%llu", "1775599305804553124", "\xc8\xee\xc8\xde\xa7\x8a\x99\xa4\x31"sv),
+TestCase("%lld", "720129488086281194", "%llu", "720129488086281194", "\xd4\xaf\xd5\x9a\x87\xfd\xb4\xfe\x13"sv),
+TestCase("%lld", "6711270492673007555", "%llu", "6711270492673007555", "\x86\xff\xae\xb5\x91\xbd\x9c\xa3\xba\x01"sv),
+TestCase("%lld", "7513165068981918756", "%llu", "7513165068981918756", "\xc8\xf0\xd2\xb8\xdc\xf4\x8f\xc4\xd0\x01"sv),
+TestCase("%lld", "2983712669290254370", "%llu", "2983712669290254370", "\xc4\xc0\x86\xce\xa8\xb7\xa3\xe8\x52"sv),
+TestCase("%lld", "3737216259576487553", "%llu", "3737216259576487553", "\x82\xfa\xf6\x8d\xe6\x98\xa1\xdd\x67"sv),
+TestCase("%lld", "2274224013798231728", "%llu", "2274224013798231728", "\xe0\xba\xda\xa1\x9e\xac\xd5\x8f\x3f"sv),
+TestCase("%lld", "3565198557459535882", "%llu", "3565198557459535882", "\x94\xe0\xe2\xb1\xdc\xcc\x90\xfa\x62"sv),
+TestCase("%lld", "3732880420468774142", "%llu", "3732880420468774142", "\xfc\xd3\xea\xb0\xdf\xbd\xed\xcd\x67"sv),
+TestCase("%lld", "5371965991102827603", "%llu", "5371965991102827603", "\xa6\xb1\xce\xd2\xec\xa6\x87\x8d\x95\x01"sv),
+TestCase("%lld", "484335498791767840", "%llu", "484335498791767840", "\xc0\xbc\xc2\xd9\xbd\xa4\xda\xb8\x0d"sv),
+TestCase("%lld", "5776656679787538668", "%llu", "5776656679787538668", "\xd8\xa3\xfd\xc1\x9d\xa9\xe7\xaa\xa0\x01"sv),
+TestCase("%lld", "7846457973737734692", "%llu", "7846457973737734692", "\xc8\x88\xda\xda\x8d\xfd\x9b\xe4\xd9\x01"sv),
+TestCase("%lld", "749331093609658474", "%llu", "749331093609658474", "\xd4\xe1\x91\xdc\x9a\xaa\x94\xe6\x14"sv),
+TestCase("%lld", "4154208822466872538", "%llu", "4154208822466872538", "\xb4\x93\xf4\x9e\xc0\xba\xdb\xa6\x73"sv),
+TestCase("%lld", "8161063962699005564", "%llu", "8161063962699005564", "\xf8\xf9\xd3\xde\xea\x9b\xf6\xc1\xe2\x01"sv),
+TestCase("%lld", "3729503431677340594", "%llu", "3729503431677340594", "\xe4\xae\xd3\x9b\x90\xe7\xed\xc1\x67"sv),
+TestCase("%lld", "3621720449882747425", "%llu", "3621720449882747425", "\xc2\xc8\xbb\xb0\xef\xe3\xf7\xc2\x64"sv),
+TestCase("%lld", "3654452981777753952", "%llu", "3654452981777753952", "\xc0\x9d\xd7\xb3\xfe\xe7\x9c\xb7\x65"sv),
+TestCase("%lld", "8747087227033699739", "%llu", "8747087227033699739", "\xb6\x96\x9a\xab\xa5\xdf\xf2\xe3\xf2\x01"sv),
+TestCase("%lld", "7841542346336811454", "%llu", "7841542346336811454", "\xfc\xe6\xb5\xc9\xf7\xcd\xe0\xd2\xd9\x01"sv),
+TestCase("%lld", "8253451662283365177", "%llu", "8253451662283365177", "\xf2\xec\x8e\xd4\xdc\xa3\x93\x8a\xe5\x01"sv),
+TestCase("%lld", "3367538680468566654", "%llu", "3367538680468566654", "\xfc\xc9\xbb\x8b\xd0\xa5\xf3\xbb\x5d"sv),
+TestCase("%lld", "134795648044988988", "%llu", "134795648044988988", "\xf8\xe8\xeb\xef\xaf\xfb\xf1\xde\x03"sv),
+TestCase("%lld", "2210978119973242473", "%llu", "2210978119973242473", "\xd2\x99\xd4\xf2\xd8\xb8\xfc\xae\x3d"sv),
+TestCase("%lld", "421750780720578633", "%llu", "421750780720578633", "\x92\x81\xc0\xc9\xb7\x86\xae\xda\x0b"sv),
+TestCase("%lld", "5885301061939406661", "%llu", "5885301061939406661", "\x8a\x9d\x85\x92\xcb\x88\xe5\xac\xa3\x01"sv),
+TestCase("%lld", "8457438023241447265", "%llu", "8457438023241447265", "\xc2\xdd\x81\xd3\xd3\xc4\xed\xde\xea\x01"sv),
+TestCase("%lld", "8625342049665019705", "%llu", "8625342049665019705", "\xf2\xbc\xaf\x88\xa5\xb9\xaf\xb3\xef\x01"sv),
+TestCase("%lld", "4522738759120336903", "%llu", "4522738759120336903", "\x8e\xe0\x91\xd4\x8b\xbc\xff\xc3\x7d"sv),
+TestCase("%lld", "2671059116919281788", "%llu", "2671059116919281788", "\xf8\xe1\xd6\xe0\xad\x87\xc1\x91\x4a"sv),
+TestCase("%lld", "3110596655769706088", "%llu", "3110596655769706088", "\xd0\xf9\xde\xaa\xb0\xcb\x87\xab\x56"sv),
+TestCase("%lld", "7187907223546304710", "%llu", "7187907223546304710", "\x8c\xe3\x88\x9f\xac\xe2\xc9\xc0\xc7\x01"sv),
+TestCase("%lld", "6450021345187045785", "%llu", "6450021345187045785", "\xb2\x86\xfd\x98\xac\x8f\x8a\x83\xb3\x01"sv),
+TestCase("%lld", "1678455339349899965", "%llu", "1678455339349899965", "\xfa\xea\xf7\x94\xa0\x8f\x89\xcb\x2e"sv),
+TestCase("%lld", "8586083292869710126", "%llu", "8586083292869710126", "\xdc\xc4\xd2\xb6\xf1\xd0\xf2\xa7\xee\x01"sv),
+TestCase("%lld", "7328869729981674031", "%llu", "7328869729981674031", "\xde\xc8\xdd\xca\x8f\x8c\xb0\xb5\xcb\x01"sv),
+TestCase("%lld", "88833738086841272", "%llu", "88833738086841272", "\xf0\xee\xe3\xab\x8d\xf4\xcc\xbb\x02"sv),
+TestCase("%lld", "3875204489276489361", "%llu", "3875204489276489361", "\xa2\xfa\x97\xf5\xf0\xfc\xbe\xc7\x6b"sv),
+TestCase("%lld", "349662797203771308", "%llu", "349662797203771308", "\xd8\xbe\x80\xa8\xc4\x9d\xa0\xda\x09"sv),
+TestCase("%lld", "7970924659150689441", "%llu", "7970924659150689441", "\xc2\xd2\xa0\x81\xdd\xef\xb4\x9e\xdd\x01"sv),
+TestCase("%lld", "163280929539230956", "%llu", "163280929539230956", "\xd8\xb3\xc0\xbc\xfd\xc8\x8b\xc4\x04"sv),
+TestCase("%lld", "1719947238548425297", "%llu", "1719947238548425297", "\xa2\xa9\xbe\xa1\xd3\xb9\xbd\xde\x2f"sv),
+TestCase("%lld", "4371241115470848126", "%llu", "4371241115470848126", "\xfc\x81\xc7\xb3\xcf\xa8\xe2\xa9\x79"sv),
+TestCase("%lld", "4845845044868885160", "%llu", "4845845044868885160", "\xd0\xaa\xf9\xff\x9a\x99\xf3\xbf\x86\x01"sv),
+TestCase("%lld", "428374782176628108", "%llu", "428374782176628108", "\x98\xb6\xfc\xa5\x88\xa6\xf2\xf1\x0b"sv),
+TestCase("%lld", "7356256820074242781", "%llu", "7356256820074242781", "\xba\x8b\x8f\xd1\xc9\xa6\xd6\x96\xcc\x01"sv),
+TestCase("%lld", "7023621621475593673", "%llu", "7023621621475593673", "\x92\xb7\xf2\x8c\xdd\xa9\xf5\xf8\xc2\x01"sv),
+TestCase("%lld", "844047554037800681", "%llu", "844047554037800681", "\xd2\xfb\xae\xff\xe8\xb1\xd4\xb6\x17"sv),
+TestCase("%lld", "817568236458138451", "%llu", "817568236458138451", "\xa6\xed\xd6\x9b\xd7\xfe\xca\xd8\x16"sv),
+TestCase("%lld", "8750594193206555908", "%llu", "8750594193206555908", "\x88\xd4\xe1\xba\xcb\xc3\xad\xf0\xf2\x01"sv),
+TestCase("%lld", "8310288564467194046", "%llu", "8310288564467194046", "\xfc\xe2\xf6\xa0\xed\xda\x89\xd4\xe6\x01"sv),
+TestCase("%lld", "6530303901192542500", "%llu", "6530303901192542500", "\xc8\xe4\xee\x9e\x92\xb3\xa6\xa0\xb5\x01"sv),
+TestCase("%lld", "7528511123548308292", "%llu", "7528511123548308292", "\x88\x8d\xe4\xea\xd4\xbe\xd2\xfa\xd0\x01"sv),
+TestCase("%lld", "9162331973142792201", "%llu", "9162331973142792201", "\x92\xa0\x8a\xd6\xd5\x98\x92\xa7\xfe\x01"sv),
+TestCase("%lld", "2011986664423345991", "%llu", "2011986664423345991", "\x8e\xbd\xb6\x9c\xc3\xcd\x81\xec\x37"sv),
+TestCase("%lld", "9019302408819509", "%llu", "9019302408819509", "\xea\xcc\x98\xc3\xbf\xc0\x85\x20"sv),
+TestCase("%lld", "789517241633323881", "%llu", "789517241633323881", "\xd2\xed\xcd\xb9\xf1\xef\xf6\xf4\x15"sv),
+TestCase("%lld", "5000802077860900174", "%llu", "5000802077860900174", "\x9c\xb5\xf9\xae\xd2\xbf\xb5\xe6\x8a\x01"sv),
+TestCase("%lld", "634081739389621380", "%llu", "634081739389621380", "\x88\xd2\x8a\xae\xf0\xfe\xda\xcc\x11"sv),
+TestCase("%lld", "112762393776366951", "%llu", "112762393776366951", "\xce\x95\x81\x91\x97\xb3\xce\x90\x03"sv),
+TestCase("%lld", "6054079168136366365", "%llu", "6054079168136366365", "\xba\xa4\xe2\xad\xae\xbb\xb4\x84\xa8\x01"sv),
+TestCase("%lld", "3980060469571494571", "%llu", "3980060469571494571", "\xd6\x8a\x91\x96\x9d\xfa\x81\xbc\x6e"sv),
+TestCase("%lld", "5966260566188732596", "%llu", "5966260566188732596", "\xe8\xc2\xcd\xd1\xfa\x97\xb5\xcc\xa5\x01"sv),
+TestCase("%lld", "7994806728603097981", "%llu", "7994806728603097981", "\xfa\xed\xd2\xa4\x90\x97\xa1\xf3\xdd\x01"sv),
+TestCase("%lld", "4215789446045917951", "%llu", "4215789446045917951", "\xfe\x8b\xc3\xb6\xc7\x8a\xbf\x81\x75"sv),
+TestCase("%lld", "3106694682520283096", "%llu", "3106694682520283096", "\xb0\x9f\xbb\x93\xd2\x96\x99\x9d\x56"sv),
+TestCase("%lld", "7417099544643803895", "%llu", "7417099544643803895", "\xee\xeb\xa4\xe3\xa0\xaf\xea\xee\xcd\x01"sv),
+TestCase("%lld", "2442715921212337368", "%llu", "2442715921212337368", "\xb0\xb3\xeb\xb0\x86\xcc\xa2\xe6\x43"sv),
+TestCase("%lld", "3969107660140201186", "%llu", "3969107660140201186", "\xc4\x93\x82\xdf\xe7\x98\x8d\x95\x6e"sv),
+TestCase("%lld", "2820313669595847223", "%llu", "2820313669595847223", "\xee\xe8\x82\xf7\xdf\x95\xe2\xa3\x4e"sv),
+TestCase("%lld", "1153942061124594568", "%llu", "1153942061124594568", "\x90\xae\xb5\xcb\x9a\x8c\xd0\x83\x20"sv),
+TestCase("%lld", "7255377737678567782", "%llu", "7255377737678567782", "\xcc\xe5\xdd\xd9\x93\xe7\xa3\xb0\xc9\x01"sv),
+TestCase("%lld", "8010794524226708564", "%llu", "8010794524226708564", "\xa8\x91\xeb\x92\xa8\xcb\x87\xac\xde\x01"sv),
+TestCase("%lld", "2397984212273136806", "%llu", "2397984212273136806", "\xcc\x92\xc8\xd8\xf3\xfb\xac\xc7\x42"sv),
+TestCase("%lld", "3911421735689494721", "%llu", "3911421735689494721", "\x82\x83\x9d\xa8\x8a\xd6\x94\xc8\x6c"sv),
+TestCase("%lld", "8110862279364352221", "%llu", "8110862279364352221", "\xba\xe3\x84\xfa\xa2\x91\xc9\x8f\xe1\x01"sv),
+TestCase("%lld", "8302240689147774718", "%llu", "8302240689147774718", "\xfc\x9b\x94\xc7\xed\xfa\xbd\xb7\xe6\x01"sv),
+TestCase("%lld", "8137682451091990657", "%llu", "8137682451091990657", "\x82\xa2\xa5\x84\xde\xc4\xed\xee\xe1\x01"sv),
+TestCase("%lld", "7692576112763082769", "%llu", "7692576112763082769", "\xa2\xe0\xb0\x8e\xf6\xcd\xc2\xc1\xd5\x01"sv),
+TestCase("%lld", "4332208573039164264", "%llu", "4332208573039164264", "\xd0\xed\xb5\x8e\xd0\xaf\x8c\x9f\x78"sv),
+TestCase("%lld", "1408790260442838317", "%llu", "1408790260442838317", "\xda\xd4\xcd\xcb\xe3\xd1\x83\x8d\x27"sv),
+TestCase("%lld", "674974620879983391", "%llu", "674974620879983391", "\xbe\xcc\xa4\xdf\xed\xf5\xfe\xdd\x12"sv),
+TestCase("%lld", "2717882686191243732", "%llu", "2717882686191243732", "\xa8\xc7\xe8\xb9\xe6\xf9\xed\xb7\x4b"sv),
+TestCase("%lld", "2252874181081070317", "%llu", "2252874181081070317", "\xda\xeb\xe3\xef\xb4\xc8\xe8\xc3\x3e"sv),
+TestCase("%lld", "561600661159813802", "%llu", "561600661159813802", "\xd4\x8a\xf1\xa0\xed\xb4\x9a\xcb\x0f"sv),
+TestCase("%lld", "1743861870362420461", "%llu", "1743861870362420461", "\xda\x93\xe7\x84\xb7\xc8\xb8\xb3\x30"sv),
+TestCase("%lld", "469546530241540223", "%llu", "469546530241540223", "\xfe\xc1\x83\xc9\x9b\x85\x95\x84\x0d"sv),
+TestCase("%lld", "2803811754125283747", "%llu", "2803811754125283747", "\xc6\xc6\x9e\xdb\xec\xfb\x91\xe9\x4d"sv),
+TestCase("%lld", "1450094191759522064", "%llu", "1450094191759522064", "\xa0\xe4\xf7\xea\x80\xbf\xe2\x9f\x28"sv),
+TestCase("%lld", "973928254324237827", "%llu", "973928254324237827", "\x86\x98\xb3\xbb\xca\xa5\x8b\x84\x1b"sv),
+TestCase("%lld", "5007711289774136962", "%llu", "5007711289774136962", "\x84\xfa\x9a\xcb\xda\xb8\xfb\xfe\x8a\x01"sv),
+TestCase("%lld", "5617796192393039749", "%llu", "5617796192393039749", "\x8a\xbe\x88\xc2\xec\xf7\xb5\xf6\x9b\x01"sv),
+TestCase("%lld", "7609052204185497138", "%llu", "7609052204185497138", "\xe4\xb8\x89\xbe\xc9\xaa\xe4\x98\xd3\x01"sv),
+TestCase("%lld", "4109234434773033977", "%llu", "4109234434773033977", "\xf2\x8f\x99\xf7\xcb\xbc\xf7\x86\x72"sv),
+TestCase("%lld", "6196547806636196643", "%llu", "6196547806636196643", "\xc6\x8c\xb6\x84\xc8\xd9\xc7\xfe\xab\x01"sv),
+TestCase("%lld", "1242378025984660164", "%llu", "1242378025984660164", "\x88\xcb\x88\xb9\xee\x8e\xe8\xbd\x22"sv),
+TestCase("%lld", "3684142195824821322", "%llu", "3684142195824821322", "\x94\x91\xd4\xc2\xd8\xf3\xd9\xa0\x66"sv),
+TestCase("%lld", "7118933327258815856", "%llu", "7118933327258815856", "\xe0\xb5\x9c\xef\x96\x89\xc4\xcb\xc5\x01"sv),
+TestCase("%lld", "230557021351043665", "%llu", "230557021351043665", "\xa2\x89\xcf\xe1\xf5\x98\x8d\xb3\x06"sv),
+TestCase("%lld", "8445293705764922090", "%llu", "8445293705764922090", "\xd4\x8b\xaf\xbf\xab\xf8\xda\xb3\xea\x01"sv),
+TestCase("%lld", "1574191283697066832", "%llu", "1574191283697066832", "\xa0\x8d\xbd\x94\xba\xa8\xd3\xd8\x2b"sv),
+TestCase("%lld", "6141978416086614643", "%llu", "6141978416086614643", "\xe6\x89\xac\xcf\xfd\xb4\xd8\xbc\xaa\x01"sv),
+TestCase("%lld", "2281449725566748391", "%llu", "2281449725566748391", "\xce\x9b\x8c\x95\x82\x9c\xab\xa9\x3f"sv),
+TestCase("%lld", "784722535322631703", "%llu", "784722535322631703", "\xae\xf8\xb1\xd2\x9f\xbf\xf2\xe3\x15"sv),
+TestCase("%lld", "5758531472775216899", "%llu", "5758531472775216899", "\x86\xfc\xe3\xea\xa9\xf7\xb4\xea\x9f\x01"sv),
+TestCase("%lld", "3230692938369337673", "%llu", "3230692938369337673", "\x92\xa5\xf0\x9d\x89\x87\xdd\xd5\x59"sv),
+TestCase("%lld", "6060043374493141457", "%llu", "6060043374493141457", "\xa2\xa7\xfc\xc1\xee\xd5\xcc\x99\xa8\x01"sv),
+TestCase("%lld", "2194452418321024659", "%llu", "2194452418321024659", "\xa6\x9a\xac\xe8\xa0\xb6\xa1\xf4\x3c"sv),
+TestCase("%lld", "1179720724324426892", "%llu", "1179720724324426892", "\x98\x92\xb1\xb8\xf2\xef\x9a\xdf\x20"sv),
+TestCase("%lld", "6869292628752205971", "%llu", "6869292628752205971", "\xa6\xa2\xe0\xaf\x86\xd0\xd0\xd4\xbe\x01"sv),
+TestCase("%lld", "66666931390606463", "%llu", "66666931390606463", "\xfe\xb1\xeb\xbc\x91\xce\xec\xec\x01"sv),
+
+// Random 32-bit ints
+TestCase("%d", "548530896", "%u", "548530896", "\xa0\xab\x8f\x8b\x04"sv),
+TestCase("%d", "821325877", "%u", "821325877", "\xea\xc0\xa3\x8f\x06"sv),
+TestCase("%d", "511383431", "%u", "511383431", "\x8e\xde\xd8\xe7\x03"sv),
+TestCase("%d", "73179717", "%u", "73179717", "\x8a\x89\xe5\x45"sv),
+TestCase("%d", "644966342", "%u", "644966342", "\x8c\x9f\x8b\xe7\x04"sv),
+TestCase("%d", "1980405737", "%u", "1980405737", "\xd2\xdf\xd4\xe0\x0e"sv),
+TestCase("%d", "476646907", "%u", "476646907", "\xf6\xb7\xc8\xc6\x03"sv),
+TestCase("%d", "2032236920", "%u", "2032236920", "\xf0\xe5\x8b\x92\x0f"sv),
+TestCase("%d", "1293986078", "%u", "1293986078", "\xbc\xa4\x85\xd2\x09"sv),
+TestCase("%d", "301233235", "%u", "301233235", "\xa6\xd1\xa3\x9f\x02"sv),
+TestCase("%d", "558217992", "%u", "558217992", "\x90\xec\xad\x94\x04"sv),
+TestCase("%d", "2097556487", "%u", "2097556487", "\x8e\xb0\xb1\xd0\x0f"sv),
+TestCase("%d", "1914275808", "%u", "1914275808", "\xc0\x9f\xcc\xa1\x0e"sv),
+TestCase("%d", "817548252", "%u", "817548252", "\xb8\xaf\xd6\x8b\x06"sv),
+TestCase("%d", "409952181", "%u", "409952181", "\xea\xfe\xfa\x86\x03"sv),
+TestCase("%d", "1247456066", "%u", "1247456066", "\x84\xad\xd5\xa5\x09"sv),
+TestCase("%d", "1516178395", "%u", "1516178395", "\xb6\xaf\xf8\xa5\x0b"sv),
+TestCase("%d", "119460598", "%u", "119460598", "\xec\xcb\xf6\x71"sv),
+TestCase("%d", "1712502589", "%u", "1712502589", "\xfa\xdc\x95\xe1\x0c"sv),
+TestCase("%d", "1688223314", "%u", "1688223314", "\xa4\xf9\x81\xca\x0c"sv),
+TestCase("%d", "706807765", "%u", "706807765", "\xaa\x9f\x88\xa2\x05"sv),
+TestCase("%d", "1774808552", "%u", "1774808552", "\xd0\xb7\xcb\x9c\x0d"sv),
+TestCase("%d", "1789229650", "%u", "1789229650", "\xa4\xe9\xab\xaa\x0d"sv),
+TestCase("%d", "1849514292", "%u", "1849514292", "\xe8\xe4\xea\xe3\x0d"sv),
+TestCase("%d", "1655538874", "%u", "1655538874", "\xf4\x92\xec\xaa\x0c"sv),
+TestCase("%d", "34451703", "%u", "34451703", "\xee\xc3\xed\x20"sv),
+TestCase("%d", "1217027822", "%u", "1217027822", "\xdc\xfb\xd2\x88\x09"sv),
+TestCase("%d", "1722335750", "%u", "1722335750", "\x8c\x88\xc6\xea\x0c"sv),
+TestCase("%d", "1755007515", "%u", "1755007515", "\xb6\xa8\xda\x89\x0d"sv),
+TestCase("%d", "1016776167", "%u", "1016776167", "\xce\x97\xd6\xc9\x07"sv),
+TestCase("%d", "2053515555", "%u", "2053515555", "\xc6\xa4\xb1\xa6\x0f"sv),
+TestCase("%d", "652344632", "%u", "652344632", "\xf0\xf4\x8f\xee\x04"sv),
+TestCase("%d", "2075650855", "%u", "2075650855", "\xce\xac\xbf\xbb\x0f"sv),
+TestCase("%d", "1374899794", "%u", "1374899794", "\xa4\xb9\x9a\x9f\x0a"sv),
+TestCase("%d", "631591603", "%u", "631591603", "\xe6\xca\xaa\xda\x04"sv),
+TestCase("%d", "1017271797", "%u", "1017271797", "\xea\xd7\x92\xca\x07"sv),
+TestCase("%d", "659199737", "%u", "659199737", "\xf2\xdb\xd4\xf4\x04"sv),
+TestCase("%d", "1782422140", "%u", "1782422140", "\xf8\xe9\xec\xa3\x0d"sv),
+TestCase("%d", "1572071831", "%u", "1572071831", "\xae\xa6\x9f\xdb\x0b"sv),
+TestCase("%d", "2105608301", "%u", "2105608301", "\xda\xa1\x88\xd8\x0f"sv),
+TestCase("%d", "1534509283", "%u", "1534509283", "\xc6\x83\xb6\xb7\x0b"sv),
+TestCase("%d", "1360016822", "%u", "1360016822", "\xec\xd6\x81\x91\x0a"sv),
+TestCase("%d", "66588090", "%u", "66588090", "\xf4\xb6\xc0\x3f"sv),
+TestCase("%d", "2010287594", "%u", "2010287594", "\xd4\xb7\x94\xfd\x0e"sv),
+TestCase("%d", "1409055793", "%u", "1409055793", "\xe2\xf0\xe3\xbf\x0a"sv),
+TestCase("%d", "1312806256", "%u", "1312806256", "\xe0\xd5\xfe\xe3\x09"sv),
+TestCase("%d", "117699373", "%u", "117699373", "\xda\xcc\x9f\x70"sv),
+TestCase("%d", "1875416592", "%u", "1875416592", "\xa0\xd8\xc4\xfc\x0d"sv),
+TestCase("%d", "550403142", "%u", "550403142", "\x8c\xf1\xf3\x8c\x04"sv),
+TestCase("%d", "1556101213", "%u", "1556101213", "\xba\xe1\x81\xcc\x0b"sv),
+TestCase("%d", "1397393149", "%u", "1397393149", "\xfa\x9b\xd4\xb4\x0a"sv),
+TestCase("%d", "504462025", "%u", "504462025", "\x92\xeb\x8b\xe1\x03"sv),
+TestCase("%d", "1578855448", "%u", "1578855448", "\xb0\xb0\xdb\xe1\x0b"sv),
+TestCase("%d", "341299952", "%u", "341299952", "\xe0\xcb\xbe\xc5\x02"sv),
+TestCase("%d", "554765077", "%u", "554765077", "\xaa\xac\x88\x91\x04"sv),
+TestCase("%d", "1581023732", "%u", "1581023732", "\xe8\x87\xe4\xe3\x0b"sv),
+TestCase("%d", "1415869606", "%u", "1415869606", "\xcc\xd2\xa3\xc6\x0a"sv),
+TestCase("%d", "724653249", "%u", "724653249", "\x82\xd3\x8a\xb3\x05"sv),
+TestCase("%d", "88302425", "%u", "88302425", "\xb2\x8d\x9b\x54"sv),
+TestCase("%d", "1742669733", "%u", "1742669733", "\xca\x9e\xf8\xfd\x0c"sv),
+TestCase("%d", "878166891", "%u", "878166891", "\xd6\x8d\xbe\xc5\x06"sv),
+TestCase("%d", "1416428619", "%u", "1416428619", "\x96\xf1\xe7\xc6\x0a"sv),
+TestCase("%d", "1034414669", "%u", "1034414669", "\x9a\xa9\xbf\xda\x07"sv),
+TestCase("%d", "905412084", "%u", "905412084", "\xe8\xf7\xbb\xdf\x06"sv),
+TestCase("%d", "63837368", "%u", "63837368", "\xf0\xd2\xf0\x3c"sv),
+TestCase("%d", "645161396", "%u", "645161396", "\xe8\x86\xa3\xe7\x04"sv),
+TestCase("%d", "2107597739", "%u", "2107597739", "\xd6\x8e\xfb\xd9\x0f"sv),
+TestCase("%d", "76301137", "%u", "76301137", "\xa2\x8d\xe2\x48"sv),
+TestCase("%d", "459966688", "%u", "459966688", "\xc0\xa3\xd4\xb6\x03"sv),
+TestCase("%d", "1966018032", "%u", "1966018032", "\xe0\xb7\xf8\xd2\x0e"sv),
+TestCase("%d", "1781593154", "%u", "1781593154", "\x84\xd1\x87\xa3\x0d"sv),
+TestCase("%d", "558910911", "%u", "558910911", "\xfe\xb6\x82\x95\x04"sv),
+TestCase("%d", "1968681756", "%u", "1968681756", "\xb8\xcc\xbd\xd5\x0e"sv),
+TestCase("%d", "1898903331", "%u", "1898903331", "\xc6\xdc\xf7\x92\x0e"sv),
+TestCase("%d", "667424750", "%u", "667424750", "\xdc\xdf\xc0\xfc\x04"sv),
+TestCase("%d", "1831749058", "%u", "1831749058", "\x84\x97\xf2\xd2\x0d"sv),
+TestCase("%d", "1630416710", "%u", "1630416710", "\x8c\xbd\xf1\x92\x0c"sv),
+TestCase("%d", "1774702744", "%u", "1774702744", "\xb0\xc2\xbe\x9c\x0d"sv),
+TestCase("%d", "46739084", "%u", "46739084", "\x98\xba\xc9\x2c"sv),
+TestCase("%d", "709255105", "%u", "709255105", "\x82\xff\xb2\xa4\x05"sv),
+TestCase("%d", "1264949590", "%u", "1264949590", "\xac\xe5\xac\xb6\x09"sv),
+TestCase("%d", "2785021", "%u", "2785021", "\xfa\xfb\xd3\x02"sv),
+TestCase("%d", "1940076275", "%u", "1940076275", "\xe6\xdb\x99\xba\x0e"sv),
+TestCase("%d", "1436099990", "%u", "1436099990", "\xac\x96\xc9\xd9\x0a"sv),
+TestCase("%d", "622422778", "%u", "622422778", "\xf4\xab\xcb\xd1\x04"sv),
+TestCase("%d", "506192594", "%u", "506192594", "\xa4\x8b\xdf\xe2\x03"sv),
+TestCase("%d", "1329083686", "%u", "1329083686", "\xcc\xd4\xc1\xf3\x09"sv),
+TestCase("%d", "724686473", "%u", "724686473", "\x92\xda\x8e\xb3\x05"sv),
+TestCase("%d", "1693465014", "%u", "1693465014", "\xec\xe6\x81\xcf\x0c"sv),
+TestCase("%d", "986175143", "%u", "986175143", "\xce\xda\xbe\xac\x07"sv),
+TestCase("%d", "165118383", "%u", "165118383", "\xde\x86\xbc\x9d\x01"sv),
+TestCase("%d", "1364735728", "%u", "1364735728", "\xe0\xdb\xc1\x95\x0a"sv),
+TestCase("%d", "426265571", "%u", "426265571", "\xc6\xaf\xc2\x96\x03"sv),
+TestCase("%d", "1019467505", "%u", "1019467505", "\xe2\xdb\x9e\xcc\x07"sv),
+TestCase("%d", "353536599", "%u", "353536599", "\xae\xa9\x94\xd1\x02"sv),
+TestCase("%d", "1275925671", "%u", "1275925671", "\xce\xd2\xe8\xc0\x09"sv),
+TestCase("%d", "1250514267", "%u", "1250514267", "\xb6\xd5\xca\xa8\x09"sv),
+TestCase("%d", "1600951834", "%u", "1600951834", "\xb4\xd8\xe4\xf6\x0b"sv),
+TestCase("%d", "799391776", "%u", "799391776", "\xc0\x80\xae\xfa\x05"sv),
+TestCase("%d", "1812577120", "%u", "1812577120", "\xc0\xed\xcd\xc0\x0d"sv),
+
+// Random 16-bit ints
+TestCase("%d", "12031", "%u", "12031", "\xfe\xbb\x01"sv),
+TestCase("%d", "17500", "%u", "17500", "\xb8\x91\x02"sv),
+TestCase("%d", "14710", "%u", "14710", "\xec\xe5\x01"sv),
+TestCase("%d", "8546", "%u", "8546", "\xc4\x85\x01"sv),
+TestCase("%d", "25208", "%u", "25208", "\xf0\x89\x03"sv),
+TestCase("%d", "29977", "%u", "29977", "\xb2\xd4\x03"sv),
+TestCase("%d", "31629", "%u", "31629", "\x9a\xee\x03"sv),
+TestCase("%d", "28612", "%u", "28612", "\x88\xbf\x03"sv),
+TestCase("%d", "4804", "%u", "4804", "\x88\x4b"sv),
+TestCase("%d", "7142", "%u", "7142", "\xcc\x6f"sv),
+TestCase("%d", "18567", "%u", "18567", "\x8e\xa2\x02"sv),
+TestCase("%d", "9376", "%u", "9376", "\xc0\x92\x01"sv),
+TestCase("%d", "19912", "%u", "19912", "\x90\xb7\x02"sv),
+TestCase("%d", "18", "%u", "18", "\x24"sv),
+TestCase("%d", "12097", "%u", "12097", "\x82\xbd\x01"sv),
+TestCase("%d", "4004", "%u", "4004", "\xc8\x3e"sv),
+TestCase("%d", "13241", "%u", "13241", "\xf2\xce\x01"sv),
+TestCase("%d", "17823", "%u", "17823", "\xbe\x96\x02"sv),
+TestCase("%d", "11129", "%u", "11129", "\xf2\xad\x01"sv),
+TestCase("%d", "16567", "%u", "16567", "\xee\x82\x02"sv),
+TestCase("%d", "9138", "%u", "9138", "\xe4\x8e\x01"sv),
+TestCase("%d", "31404", "%u", "31404", "\xd8\xea\x03"sv),
+TestCase("%d", "1072", "%u", "1072", "\xe0\x10"sv),
+TestCase("%d", "12870", "%u", "12870", "\x8c\xc9\x01"sv),
+TestCase("%d", "25499", "%u", "25499", "\xb6\x8e\x03"sv),
+TestCase("%d", "1640", "%u", "1640", "\xd0\x19"sv),
+TestCase("%d", "26764", "%u", "26764", "\x98\xa2\x03"sv),
+TestCase("%d", "2078", "%u", "2078", "\xbc\x20"sv),
+TestCase("%d", "10264", "%u", "10264", "\xb0\xa0\x01"sv),
+TestCase("%d", "15533", "%u", "15533", "\xda\xf2\x01"sv),
+TestCase("%d", "7701", "%u", "7701", "\xaa\x78"sv),
+TestCase("%d", "24302", "%u", "24302", "\xdc\xfb\x02"sv),
+TestCase("%d", "10568", "%u", "10568", "\x90\xa5\x01"sv),
+TestCase("%d", "2206", "%u", "2206", "\xbc\x22"sv),
+TestCase("%d", "16240", "%u", "16240", "\xe0\xfd\x01"sv),
+TestCase("%d", "16600", "%u", "16600", "\xb0\x83\x02"sv),
+TestCase("%d", "30984", "%u", "30984", "\x90\xe4\x03"sv),
+TestCase("%d", "18489", "%u", "18489", "\xf2\xa0\x02"sv),
+TestCase("%d", "8613", "%u", "8613", "\xca\x86\x01"sv),
+TestCase("%d", "26441", "%u", "26441", "\x92\x9d\x03"sv),
+TestCase("%d", "22831", "%u", "22831", "\xde\xe4\x02"sv),
+TestCase("%d", "2307", "%u", "2307", "\x86\x24"sv),
+TestCase("%d", "24179", "%u", "24179", "\xe6\xf9\x02"sv),
+TestCase("%d", "6400", "%u", "6400", "\x80\x64"sv),
+TestCase("%d", "4264", "%u", "4264", "\xd0\x42"sv),
+TestCase("%d", "20770", "%u", "20770", "\xc4\xc4\x02"sv),
+TestCase("%d", "24276", "%u", "24276", "\xa8\xfb\x02"sv),
+TestCase("%d", "27013", "%u", "27013", "\x8a\xa6\x03"sv),
+TestCase("%d", "30434", "%u", "30434", "\xc4\xdb\x03"sv),
+TestCase("%d", "8338", "%u", "8338", "\xa4\x82\x01"sv),
+TestCase("%d", "20544", "%u", "20544", "\x80\xc1\x02"sv),
+TestCase("%d", "22908", "%u", "22908", "\xf8\xe5\x02"sv),
+TestCase("%d", "7111", "%u", "7111", "\x8e\x6f"sv),
+TestCase("%d", "30015", "%u", "30015", "\xfe\xd4\x03"sv),
+TestCase("%d", "16241", "%u", "16241", "\xe2\xfd\x01"sv),
+TestCase("%d", "6262", "%u", "6262", "\xec\x61"sv),
+TestCase("%d", "30724", "%u", "30724", "\x88\xe0\x03"sv),
+TestCase("%d", "7761", "%u", "7761", "\xa2\x79"sv),
+TestCase("%d", "12306", "%u", "12306", "\xa4\xc0\x01"sv),
+TestCase("%d", "5472", "%u", "5472", "\xc0\x55"sv),
+TestCase("%d", "20833", "%u", "20833", "\xc2\xc5\x02"sv),
+TestCase("%d", "19726", "%u", "19726", "\x9c\xb4\x02"sv),
+TestCase("%d", "20971", "%u", "20971", "\xd6\xc7\x02"sv),
+TestCase("%d", "12697", "%u", "12697", "\xb2\xc6\x01"sv),
+TestCase("%d", "29872", "%u", "29872", "\xe0\xd2\x03"sv),
+TestCase("%d", "20930", "%u", "20930", "\x84\xc7\x02"sv),
+TestCase("%d", "11185", "%u", "11185", "\xe2\xae\x01"sv),
+TestCase("%d", "4894", "%u", "4894", "\xbc\x4c"sv),
+TestCase("%d", "21973", "%u", "21973", "\xaa\xd7\x02"sv),
+TestCase("%d", "32040", "%u", "32040", "\xd0\xf4\x03"sv),
+TestCase("%d", "26757", "%u", "26757", "\x8a\xa2\x03"sv),
+TestCase("%d", "18963", "%u", "18963", "\xa6\xa8\x02"sv),
+TestCase("%d", "338", "%u", "338", "\xa4\x05"sv),
+TestCase("%d", "18607", "%u", "18607", "\xde\xa2\x02"sv),
+TestCase("%d", "7633", "%u", "7633", "\xa2\x77"sv),
+TestCase("%d", "14357", "%u", "14357", "\xaa\xe0\x01"sv),
+TestCase("%d", "14672", "%u", "14672", "\xa0\xe5\x01"sv),
+TestCase("%d", "13025", "%u", "13025", "\xc2\xcb\x01"sv),
+TestCase("%d", "4413", "%u", "4413", "\xfa\x44"sv),
+TestCase("%d", "2946", "%u", "2946", "\x84\x2e"sv),
+TestCase("%d", "12641", "%u", "12641", "\xc2\xc5\x01"sv),
+TestCase("%d", "28130", "%u", "28130", "\xc4\xb7\x03"sv),
+TestCase("%d", "6892", "%u", "6892", "\xd8\x6b"sv),
+TestCase("%d", "27495", "%u", "27495", "\xce\xad\x03"sv),
+TestCase("%d", "11488", "%u", "11488", "\xc0\xb3\x01"sv),
+TestCase("%d", "23283", "%u", "23283", "\xe6\xeb\x02"sv),
+TestCase("%d", "29949", "%u", "29949", "\xfa\xd3\x03"sv),
+TestCase("%d", "5161", "%u", "5161", "\xd2\x50"sv),
+TestCase("%d", "10664", "%u", "10664", "\xd0\xa6\x01"sv),
+TestCase("%d", "22534", "%u", "22534", "\x8c\xe0\x02"sv),
+TestCase("%d", "6502", "%u", "6502", "\xcc\x65"sv),
+TestCase("%d", "28037", "%u", "28037", "\x8a\xb6\x03"sv),
+TestCase("%d", "305", "%u", "305", "\xe2\x04"sv),
+TestCase("%d", "32042", "%u", "32042", "\xd4\xf4\x03"sv),
+TestCase("%d", "8481", "%u", "8481", "\xc2\x84\x01"sv),
+TestCase("%d", "22068", "%u", "22068", "\xe8\xd8\x02"sv),
+TestCase("%d", "13788", "%u", "13788", "\xb8\xd7\x01"sv),
+TestCase("%d", "29904", "%u", "29904", "\xa0\xd3\x03"sv),
+TestCase("%d", "12689", "%u", "12689", "\xa2\xc6\x01"sv),
+TestCase("%d", "1205", "%u", "1205", "\xea\x12"sv),
+
+// All 8-bit numbers
+TestCase("%d", "-128", "%u", "4294967168", "\xff\x01"sv),
+TestCase("%d", "-127", "%u", "4294967169", "\xfd\x01"sv),
+TestCase("%d", "-126", "%u", "4294967170", "\xfb\x01"sv),
+TestCase("%d", "-125", "%u", "4294967171", "\xf9\x01"sv),
+TestCase("%d", "-124", "%u", "4294967172", "\xf7\x01"sv),
+TestCase("%d", "-123", "%u", "4294967173", "\xf5\x01"sv),
+TestCase("%d", "-122", "%u", "4294967174", "\xf3\x01"sv),
+TestCase("%d", "-121", "%u", "4294967175", "\xf1\x01"sv),
+TestCase("%d", "-120", "%u", "4294967176", "\xef\x01"sv),
+TestCase("%d", "-119", "%u", "4294967177", "\xed\x01"sv),
+TestCase("%d", "-118", "%u", "4294967178", "\xeb\x01"sv),
+TestCase("%d", "-117", "%u", "4294967179", "\xe9\x01"sv),
+TestCase("%d", "-116", "%u", "4294967180", "\xe7\x01"sv),
+TestCase("%d", "-115", "%u", "4294967181", "\xe5\x01"sv),
+TestCase("%d", "-114", "%u", "4294967182", "\xe3\x01"sv),
+TestCase("%d", "-113", "%u", "4294967183", "\xe1\x01"sv),
+TestCase("%d", "-112", "%u", "4294967184", "\xdf\x01"sv),
+TestCase("%d", "-111", "%u", "4294967185", "\xdd\x01"sv),
+TestCase("%d", "-110", "%u", "4294967186", "\xdb\x01"sv),
+TestCase("%d", "-109", "%u", "4294967187", "\xd9\x01"sv),
+TestCase("%d", "-108", "%u", "4294967188", "\xd7\x01"sv),
+TestCase("%d", "-107", "%u", "4294967189", "\xd5\x01"sv),
+TestCase("%d", "-106", "%u", "4294967190", "\xd3\x01"sv),
+TestCase("%d", "-105", "%u", "4294967191", "\xd1\x01"sv),
+TestCase("%d", "-104", "%u", "4294967192", "\xcf\x01"sv),
+TestCase("%d", "-103", "%u", "4294967193", "\xcd\x01"sv),
+TestCase("%d", "-102", "%u", "4294967194", "\xcb\x01"sv),
+TestCase("%d", "-101", "%u", "4294967195", "\xc9\x01"sv),
+TestCase("%d", "-100", "%u", "4294967196", "\xc7\x01"sv),
+TestCase("%d", "-99", "%u", "4294967197", "\xc5\x01"sv),
+TestCase("%d", "-98", "%u", "4294967198", "\xc3\x01"sv),
+TestCase("%d", "-97", "%u", "4294967199", "\xc1\x01"sv),
+TestCase("%d", "-96", "%u", "4294967200", "\xbf\x01"sv),
+TestCase("%d", "-95", "%u", "4294967201", "\xbd\x01"sv),
+TestCase("%d", "-94", "%u", "4294967202", "\xbb\x01"sv),
+TestCase("%d", "-93", "%u", "4294967203", "\xb9\x01"sv),
+TestCase("%d", "-92", "%u", "4294967204", "\xb7\x01"sv),
+TestCase("%d", "-91", "%u", "4294967205", "\xb5\x01"sv),
+TestCase("%d", "-90", "%u", "4294967206", "\xb3\x01"sv),
+TestCase("%d", "-89", "%u", "4294967207", "\xb1\x01"sv),
+TestCase("%d", "-88", "%u", "4294967208", "\xaf\x01"sv),
+TestCase("%d", "-87", "%u", "4294967209", "\xad\x01"sv),
+TestCase("%d", "-86", "%u", "4294967210", "\xab\x01"sv),
+TestCase("%d", "-85", "%u", "4294967211", "\xa9\x01"sv),
+TestCase("%d", "-84", "%u", "4294967212", "\xa7\x01"sv),
+TestCase("%d", "-83", "%u", "4294967213", "\xa5\x01"sv),
+TestCase("%d", "-82", "%u", "4294967214", "\xa3\x01"sv),
+TestCase("%d", "-81", "%u", "4294967215", "\xa1\x01"sv),
+TestCase("%d", "-80", "%u", "4294967216", "\x9f\x01"sv),
+TestCase("%d", "-79", "%u", "4294967217", "\x9d\x01"sv),
+TestCase("%d", "-78", "%u", "4294967218", "\x9b\x01"sv),
+TestCase("%d", "-77", "%u", "4294967219", "\x99\x01"sv),
+TestCase("%d", "-76", "%u", "4294967220", "\x97\x01"sv),
+TestCase("%d", "-75", "%u", "4294967221", "\x95\x01"sv),
+TestCase("%d", "-74", "%u", "4294967222", "\x93\x01"sv),
+TestCase("%d", "-73", "%u", "4294967223", "\x91\x01"sv),
+TestCase("%d", "-72", "%u", "4294967224", "\x8f\x01"sv),
+TestCase("%d", "-71", "%u", "4294967225", "\x8d\x01"sv),
+TestCase("%d", "-70", "%u", "4294967226", "\x8b\x01"sv),
+TestCase("%d", "-69", "%u", "4294967227", "\x89\x01"sv),
+TestCase("%d", "-68", "%u", "4294967228", "\x87\x01"sv),
+TestCase("%d", "-67", "%u", "4294967229", "\x85\x01"sv),
+TestCase("%d", "-66", "%u", "4294967230", "\x83\x01"sv),
+TestCase("%d", "-65", "%u", "4294967231", "\x81\x01"sv),
+TestCase("%d", "-64", "%u", "4294967232", "\x7f"sv),
+TestCase("%d", "-63", "%u", "4294967233", "\x7d"sv),
+TestCase("%d", "-62", "%u", "4294967234", "\x7b"sv),
+TestCase("%d", "-61", "%u", "4294967235", "\x79"sv),
+TestCase("%d", "-60", "%u", "4294967236", "\x77"sv),
+TestCase("%d", "-59", "%u", "4294967237", "\x75"sv),
+TestCase("%d", "-58", "%u", "4294967238", "\x73"sv),
+TestCase("%d", "-57", "%u", "4294967239", "\x71"sv),
+TestCase("%d", "-56", "%u", "4294967240", "\x6f"sv),
+TestCase("%d", "-55", "%u", "4294967241", "\x6d"sv),
+TestCase("%d", "-54", "%u", "4294967242", "\x6b"sv),
+TestCase("%d", "-53", "%u", "4294967243", "\x69"sv),
+TestCase("%d", "-52", "%u", "4294967244", "\x67"sv),
+TestCase("%d", "-51", "%u", "4294967245", "\x65"sv),
+TestCase("%d", "-50", "%u", "4294967246", "\x63"sv),
+TestCase("%d", "-49", "%u", "4294967247", "\x61"sv),
+TestCase("%d", "-48", "%u", "4294967248", "\x5f"sv),
+TestCase("%d", "-47", "%u", "4294967249", "\x5d"sv),
+TestCase("%d", "-46", "%u", "4294967250", "\x5b"sv),
+TestCase("%d", "-45", "%u", "4294967251", "\x59"sv),
+TestCase("%d", "-44", "%u", "4294967252", "\x57"sv),
+TestCase("%d", "-43", "%u", "4294967253", "\x55"sv),
+TestCase("%d", "-42", "%u", "4294967254", "\x53"sv),
+TestCase("%d", "-41", "%u", "4294967255", "\x51"sv),
+TestCase("%d", "-40", "%u", "4294967256", "\x4f"sv),
+TestCase("%d", "-39", "%u", "4294967257", "\x4d"sv),
+TestCase("%d", "-38", "%u", "4294967258", "\x4b"sv),
+TestCase("%d", "-37", "%u", "4294967259", "\x49"sv),
+TestCase("%d", "-36", "%u", "4294967260", "\x47"sv),
+TestCase("%d", "-35", "%u", "4294967261", "\x45"sv),
+TestCase("%d", "-34", "%u", "4294967262", "\x43"sv),
+TestCase("%d", "-33", "%u", "4294967263", "\x41"sv),
+TestCase("%d", "-32", "%u", "4294967264", "\x3f"sv),
+TestCase("%d", "-31", "%u", "4294967265", "\x3d"sv),
+TestCase("%d", "-30", "%u", "4294967266", "\x3b"sv),
+TestCase("%d", "-29", "%u", "4294967267", "\x39"sv),
+TestCase("%d", "-28", "%u", "4294967268", "\x37"sv),
+TestCase("%d", "-27", "%u", "4294967269", "\x35"sv),
+TestCase("%d", "-26", "%u", "4294967270", "\x33"sv),
+TestCase("%d", "-25", "%u", "4294967271", "\x31"sv),
+TestCase("%d", "-24", "%u", "4294967272", "\x2f"sv),
+TestCase("%d", "-23", "%u", "4294967273", "\x2d"sv),
+TestCase("%d", "-22", "%u", "4294967274", "\x2b"sv),
+TestCase("%d", "-21", "%u", "4294967275", "\x29"sv),
+TestCase("%d", "-20", "%u", "4294967276", "\x27"sv),
+TestCase("%d", "-19", "%u", "4294967277", "\x25"sv),
+TestCase("%d", "-18", "%u", "4294967278", "\x23"sv),
+TestCase("%d", "-17", "%u", "4294967279", "\x21"sv),
+TestCase("%d", "-16", "%u", "4294967280", "\x1f"sv),
+TestCase("%d", "-15", "%u", "4294967281", "\x1d"sv),
+TestCase("%d", "-14", "%u", "4294967282", "\x1b"sv),
+TestCase("%d", "-13", "%u", "4294967283", "\x19"sv),
+TestCase("%d", "-12", "%u", "4294967284", "\x17"sv),
+TestCase("%d", "-11", "%u", "4294967285", "\x15"sv),
+TestCase("%d", "-10", "%u", "4294967286", "\x13"sv),
+TestCase("%d", "-9", "%u", "4294967287", "\x11"sv),
+TestCase("%d", "-8", "%u", "4294967288", "\x0f"sv),
+TestCase("%d", "-7", "%u", "4294967289", "\x0d"sv),
+TestCase("%d", "-6", "%u", "4294967290", "\x0b"sv),
+TestCase("%d", "-5", "%u", "4294967291", "\x09"sv),
+TestCase("%d", "-4", "%u", "4294967292", "\x07"sv),
+TestCase("%d", "-3", "%u", "4294967293", "\x05"sv),
+TestCase("%d", "-2", "%u", "4294967294", "\x03"sv),
+TestCase("%d", "-1", "%u", "4294967295", "\x01"sv),
+TestCase("%d", "0", "%u", "0", "\x00"sv),
+TestCase("%d", "1", "%u", "1", "\x02"sv),
+TestCase("%d", "2", "%u", "2", "\x04"sv),
+TestCase("%d", "3", "%u", "3", "\x06"sv),
+TestCase("%d", "4", "%u", "4", "\x08"sv),
+TestCase("%d", "5", "%u", "5", "\x0a"sv),
+TestCase("%d", "6", "%u", "6", "\x0c"sv),
+TestCase("%d", "7", "%u", "7", "\x0e"sv),
+TestCase("%d", "8", "%u", "8", "\x10"sv),
+TestCase("%d", "9", "%u", "9", "\x12"sv),
+TestCase("%d", "10", "%u", "10", "\x14"sv),
+TestCase("%d", "11", "%u", "11", "\x16"sv),
+TestCase("%d", "12", "%u", "12", "\x18"sv),
+TestCase("%d", "13", "%u", "13", "\x1a"sv),
+TestCase("%d", "14", "%u", "14", "\x1c"sv),
+TestCase("%d", "15", "%u", "15", "\x1e"sv),
+TestCase("%d", "16", "%u", "16", "\x20"sv),
+TestCase("%d", "17", "%u", "17", "\x22"sv),
+TestCase("%d", "18", "%u", "18", "\x24"sv),
+TestCase("%d", "19", "%u", "19", "\x26"sv),
+TestCase("%d", "20", "%u", "20", "\x28"sv),
+TestCase("%d", "21", "%u", "21", "\x2a"sv),
+TestCase("%d", "22", "%u", "22", "\x2c"sv),
+TestCase("%d", "23", "%u", "23", "\x2e"sv),
+TestCase("%d", "24", "%u", "24", "\x30"sv),
+TestCase("%d", "25", "%u", "25", "\x32"sv),
+TestCase("%d", "26", "%u", "26", "\x34"sv),
+TestCase("%d", "27", "%u", "27", "\x36"sv),
+TestCase("%d", "28", "%u", "28", "\x38"sv),
+TestCase("%d", "29", "%u", "29", "\x3a"sv),
+TestCase("%d", "30", "%u", "30", "\x3c"sv),
+TestCase("%d", "31", "%u", "31", "\x3e"sv),
+TestCase("%d", "32", "%u", "32", "\x40"sv),
+TestCase("%d", "33", "%u", "33", "\x42"sv),
+TestCase("%d", "34", "%u", "34", "\x44"sv),
+TestCase("%d", "35", "%u", "35", "\x46"sv),
+TestCase("%d", "36", "%u", "36", "\x48"sv),
+TestCase("%d", "37", "%u", "37", "\x4a"sv),
+TestCase("%d", "38", "%u", "38", "\x4c"sv),
+TestCase("%d", "39", "%u", "39", "\x4e"sv),
+TestCase("%d", "40", "%u", "40", "\x50"sv),
+TestCase("%d", "41", "%u", "41", "\x52"sv),
+TestCase("%d", "42", "%u", "42", "\x54"sv),
+TestCase("%d", "43", "%u", "43", "\x56"sv),
+TestCase("%d", "44", "%u", "44", "\x58"sv),
+TestCase("%d", "45", "%u", "45", "\x5a"sv),
+TestCase("%d", "46", "%u", "46", "\x5c"sv),
+TestCase("%d", "47", "%u", "47", "\x5e"sv),
+TestCase("%d", "48", "%u", "48", "\x60"sv),
+TestCase("%d", "49", "%u", "49", "\x62"sv),
+TestCase("%d", "50", "%u", "50", "\x64"sv),
+TestCase("%d", "51", "%u", "51", "\x66"sv),
+TestCase("%d", "52", "%u", "52", "\x68"sv),
+TestCase("%d", "53", "%u", "53", "\x6a"sv),
+TestCase("%d", "54", "%u", "54", "\x6c"sv),
+TestCase("%d", "55", "%u", "55", "\x6e"sv),
+TestCase("%d", "56", "%u", "56", "\x70"sv),
+TestCase("%d", "57", "%u", "57", "\x72"sv),
+TestCase("%d", "58", "%u", "58", "\x74"sv),
+TestCase("%d", "59", "%u", "59", "\x76"sv),
+TestCase("%d", "60", "%u", "60", "\x78"sv),
+TestCase("%d", "61", "%u", "61", "\x7a"sv),
+TestCase("%d", "62", "%u", "62", "\x7c"sv),
+TestCase("%d", "63", "%u", "63", "\x7e"sv),
+TestCase("%d", "64", "%u", "64", "\x80\x01"sv),
+TestCase("%d", "65", "%u", "65", "\x82\x01"sv),
+TestCase("%d", "66", "%u", "66", "\x84\x01"sv),
+TestCase("%d", "67", "%u", "67", "\x86\x01"sv),
+TestCase("%d", "68", "%u", "68", "\x88\x01"sv),
+TestCase("%d", "69", "%u", "69", "\x8a\x01"sv),
+TestCase("%d", "70", "%u", "70", "\x8c\x01"sv),
+TestCase("%d", "71", "%u", "71", "\x8e\x01"sv),
+TestCase("%d", "72", "%u", "72", "\x90\x01"sv),
+TestCase("%d", "73", "%u", "73", "\x92\x01"sv),
+TestCase("%d", "74", "%u", "74", "\x94\x01"sv),
+TestCase("%d", "75", "%u", "75", "\x96\x01"sv),
+TestCase("%d", "76", "%u", "76", "\x98\x01"sv),
+TestCase("%d", "77", "%u", "77", "\x9a\x01"sv),
+TestCase("%d", "78", "%u", "78", "\x9c\x01"sv),
+TestCase("%d", "79", "%u", "79", "\x9e\x01"sv),
+TestCase("%d", "80", "%u", "80", "\xa0\x01"sv),
+TestCase("%d", "81", "%u", "81", "\xa2\x01"sv),
+TestCase("%d", "82", "%u", "82", "\xa4\x01"sv),
+TestCase("%d", "83", "%u", "83", "\xa6\x01"sv),
+TestCase("%d", "84", "%u", "84", "\xa8\x01"sv),
+TestCase("%d", "85", "%u", "85", "\xaa\x01"sv),
+TestCase("%d", "86", "%u", "86", "\xac\x01"sv),
+TestCase("%d", "87", "%u", "87", "\xae\x01"sv),
+TestCase("%d", "88", "%u", "88", "\xb0\x01"sv),
+TestCase("%d", "89", "%u", "89", "\xb2\x01"sv),
+TestCase("%d", "90", "%u", "90", "\xb4\x01"sv),
+TestCase("%d", "91", "%u", "91", "\xb6\x01"sv),
+TestCase("%d", "92", "%u", "92", "\xb8\x01"sv),
+TestCase("%d", "93", "%u", "93", "\xba\x01"sv),
+TestCase("%d", "94", "%u", "94", "\xbc\x01"sv),
+TestCase("%d", "95", "%u", "95", "\xbe\x01"sv),
+TestCase("%d", "96", "%u", "96", "\xc0\x01"sv),
+TestCase("%d", "97", "%u", "97", "\xc2\x01"sv),
+TestCase("%d", "98", "%u", "98", "\xc4\x01"sv),
+TestCase("%d", "99", "%u", "99", "\xc6\x01"sv),
+TestCase("%d", "100", "%u", "100", "\xc8\x01"sv),
+TestCase("%d", "101", "%u", "101", "\xca\x01"sv),
+TestCase("%d", "102", "%u", "102", "\xcc\x01"sv),
+TestCase("%d", "103", "%u", "103", "\xce\x01"sv),
+TestCase("%d", "104", "%u", "104", "\xd0\x01"sv),
+TestCase("%d", "105", "%u", "105", "\xd2\x01"sv),
+TestCase("%d", "106", "%u", "106", "\xd4\x01"sv),
+TestCase("%d", "107", "%u", "107", "\xd6\x01"sv),
+TestCase("%d", "108", "%u", "108", "\xd8\x01"sv),
+TestCase("%d", "109", "%u", "109", "\xda\x01"sv),
+TestCase("%d", "110", "%u", "110", "\xdc\x01"sv),
+TestCase("%d", "111", "%u", "111", "\xde\x01"sv),
+TestCase("%d", "112", "%u", "112", "\xe0\x01"sv),
+TestCase("%d", "113", "%u", "113", "\xe2\x01"sv),
+TestCase("%d", "114", "%u", "114", "\xe4\x01"sv),
+TestCase("%d", "115", "%u", "115", "\xe6\x01"sv),
+TestCase("%d", "116", "%u", "116", "\xe8\x01"sv),
+TestCase("%d", "117", "%u", "117", "\xea\x01"sv),
+TestCase("%d", "118", "%u", "118", "\xec\x01"sv),
+TestCase("%d", "119", "%u", "119", "\xee\x01"sv),
+TestCase("%d", "120", "%u", "120", "\xf0\x01"sv),
+TestCase("%d", "121", "%u", "121", "\xf2\x01"sv),
+TestCase("%d", "122", "%u", "122", "\xf4\x01"sv),
+TestCase("%d", "123", "%u", "123", "\xf6\x01"sv),
+TestCase("%d", "124", "%u", "124", "\xf8\x01"sv),
+TestCase("%d", "125", "%u", "125", "\xfa\x01"sv),
+TestCase("%d", "126", "%u", "126", "\xfc\x01"sv),
+TestCase("%d", "127", "%u", "127", "\xfe\x01"sv),
+
+};
+
+}  // namespace pw::test::varint_decoding
diff --git a/pw_tokenizer/token_database.cc b/pw_tokenizer/token_database.cc
new file mode 100644
index 0000000..42c145b
--- /dev/null
+++ b/pw_tokenizer/token_database.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 "pw_tokenizer/token_database.h"
+
+namespace pw::tokenizer {
+
+TokenDatabase::Entry TokenDatabase::Entries::operator[](size_t index) const {
+  Iterator it = begin();
+  for (size_t i = 0; i < index; ++i) {
+    ++it;
+  }
+  return it.entry();
+}
+
+TokenDatabase::Entries TokenDatabase::Find(const uint32_t token) const {
+  Iterator first = begin();
+  while (first != end() && token > first->token) {
+    ++first;
+  }
+
+  Iterator last = first;
+  while (last != end() && token == last->token) {
+    ++last;
+  }
+
+  return Entries(first, last);
+}
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/token_database_test.cc b/pw_tokenizer/token_database_test.cc
new file mode 100644
index 0000000..0dccfb6
--- /dev/null
+++ b/pw_tokenizer/token_database_test.cc
@@ -0,0 +1,263 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 "pw_tokenizer/token_database.h"
+
+#include <cstring>
+#include <string>
+#include <string_view>
+
+#include "gtest/gtest.h"
+
+namespace pw::tokenizer {
+namespace {
+
+using namespace std::literals::string_view_literals;
+
+// Use alignas to ensure that the data is properly aligned for database entries.
+// This avoids unaligned memory reads.
+alignas(TokenDatabase::RawEntry) constexpr char kBasicData[] =
+    "TOKENS\0\0\x03\x00\x00\x00\0\0\0\0"
+    "\x01\0\0\0\0\0\0\0"
+    "\x02\0\0\0\0\0\0\0"
+    "\xFF\0\0\0\0\0\0\0"
+    "hi!\0"
+    "goodbye\0"
+    ":)";
+
+alignas(TokenDatabase::RawEntry) constexpr char kEmptyData[] =
+    "TOKENS\0\0\x00\x00\x00\x00\0\0\0";  // Last byte is null terminator.
+
+alignas(TokenDatabase::RawEntry) constexpr char kBadMagic[] =
+    "TOKENs\0\0\x03\x00\x00\x00\0\0\0\0"
+    "\x01\0\0\0\0\0\0\0"
+    "hi!\0";
+
+alignas(TokenDatabase::RawEntry) constexpr char kBadVersion[] =
+    "TOKENS\0\1\x00\0\0\0\0\0\0\0";
+
+alignas(TokenDatabase::RawEntry) constexpr char kBadEntryCount[] =
+    "TOKENS\0\0\xff\x00\x00\x00\0\0\0\0";
+
+// Use signed data and a size with the top bit set to test that the entry count
+// is read correctly, without per-byte sign extension.
+alignas(TokenDatabase::RawEntry) constexpr signed char kSignedWithTopBit[] =
+    "TOKENS\0\0\x80\x00\x00\x00\0\0\0\0"
+    // Entries
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"  // 32
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"  // 64
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"  // 96
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"
+    "TOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdateTOKNdate"  // 128
+    // Strings (empty)
+    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"  //  32
+    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"  //  64
+    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"  //  96
+    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";   // 128
+
+constexpr TokenDatabase kBasicDatabase = TokenDatabase::Create<kBasicData>();
+static_assert(kBasicDatabase.size() == 3u);
+
+TEST(TokenDatabase, EntryCount) {
+  static_assert(TokenDatabase::Create<kBasicData>().size() == 3u);
+  static_assert(TokenDatabase::Create(kEmptyData).size() == 0u);
+  EXPECT_EQ(TokenDatabase::Create<kSignedWithTopBit>().size(), 0x80u);
+}
+
+TEST(TokenDatabase, ValidCheck) {
+  char basic_data[sizeof(kBasicData)];
+  std::memcpy(basic_data, kBasicData, sizeof(basic_data));
+  EXPECT_TRUE(TokenDatabase::IsValid(basic_data));
+
+  static_assert(TokenDatabase::IsValid(kBasicData));
+  static_assert(TokenDatabase::IsValid(kEmptyData));
+  static_assert(TokenDatabase::IsValid(kSignedWithTopBit));
+
+  static_assert(!TokenDatabase::IsValid(kBadMagic));
+  static_assert(!TokenDatabase::IsValid(kBadVersion));
+  static_assert(!TokenDatabase::IsValid(kBadEntryCount));
+
+  static_assert(!TokenDatabase::IsValid("TOKENS\0\0\0\0"));  // too short
+  static_assert(!TokenDatabase::IsValid("TOKENS\0\1\0\0\0\0\0\0\0\0"));
+  static_assert(!TokenDatabase::IsValid("TOKENSv0\0\0\0\0\0\0\0\0"));
+  static_assert(!TokenDatabase::IsValid("tokens\0\0\0\0\0\0\0\0\0\0"));
+
+  // No string table; this is one byte too short.
+  static_assert(
+      !TokenDatabase::IsValid("TOKENS\0\0\x01\x00\x00\x00\0\0\0\0WXYZdate"sv));
+
+  // Add one byte for the string table.
+  static_assert(
+      TokenDatabase::IsValid("TOKENS\0\0\x01\x00\x00\x00\0\0\0\0WXYZdate\0"sv));
+
+  static_assert(
+      !TokenDatabase::IsValid("TOKENS\0\0\x02\x00\x00\x00\0\0\0\0"
+                              "WXYZdate"
+                              "WXYZdate"
+                              "\0"sv));
+  static_assert(
+      TokenDatabase::IsValid("TOKENS\0\0\x02\x00\x00\x00\0\0\0\0"
+                             "WXYZdate"
+                             "WXYZdate"
+                             "hi\0\0"sv));
+  static_assert(
+      TokenDatabase::IsValid("TOKENS\0\0\x02\x00\x00\x00\0\0\0\0"
+                             "WXYZdate"
+                             "WXYZdate"
+                             "hi\0hello\0"sv));
+}
+
+TEST(TokenDatabase, Iterator) {
+  auto it = kBasicDatabase.begin();
+  EXPECT_EQ(it->token, 1u);
+  EXPECT_STREQ(it.entry().string, "hi!");
+
+  ++it;
+  EXPECT_EQ(it->token, 2u);
+  EXPECT_STREQ(it.entry().string, "goodbye");
+  EXPECT_EQ(it - kBasicDatabase.begin(), 1);
+
+  ++it;
+  EXPECT_EQ(it->token, 0xFFu);
+  EXPECT_STREQ(it.entry().string, ":)");
+  EXPECT_EQ(it - kBasicDatabase.begin(), 2);
+
+  ++it;
+  EXPECT_EQ(it, kBasicDatabase.end());
+  EXPECT_EQ(static_cast<size_t>(it - kBasicDatabase.begin()),
+            kBasicDatabase.size());
+}
+
+TEST(TokenDatabase, SingleEntryLookup_FirstEntry) {
+  auto match = kBasicDatabase.Find(1);
+  ASSERT_EQ(match.size(), 1u);
+  EXPECT_FALSE(match.empty());
+  EXPECT_STREQ(match[0].string, "hi!");
+
+  for (const auto& entry : match) {
+    EXPECT_EQ(entry.token, 1u);
+    EXPECT_STREQ(entry.string, "hi!");
+  }
+}
+
+TEST(TokenDatabase, SingleEntryLookup_MiddleEntry) {
+  auto match = kBasicDatabase.Find(2);
+  ASSERT_EQ(match.size(), 1u);
+  EXPECT_FALSE(match.empty());
+  EXPECT_STREQ(match[0].string, "goodbye");
+}
+
+TEST(TokenDatabase, SingleEntryLookup_LastEntry) {
+  auto match = kBasicDatabase.Find(0xff);
+  ASSERT_EQ(match.size(), 1u);
+  EXPECT_STREQ(match[0].string, ":)");
+  EXPECT_FALSE(match.empty());
+}
+
+TEST(TokenDatabase, SingleEntryLookup_NonPresent) {
+  EXPECT_TRUE(kBasicDatabase.Find(0).empty());
+  EXPECT_TRUE(kBasicDatabase.Find(3).empty());
+  EXPECT_TRUE(kBasicDatabase.Find(10239).empty());
+  EXPECT_TRUE(kBasicDatabase.Find(0xFFFFFFFFu).empty());
+}
+
+TEST(TokenDatabase, SingleEntryLookup_NoMatches) {
+  // Can also create the database at runtime.
+  TokenDatabase tokens = TokenDatabase::Create(kBasicData);
+  const auto match = tokens.Find(42);
+  ASSERT_EQ(match.size(), 0u);
+  EXPECT_TRUE(match.empty());
+
+  for (const auto& entry : match) {
+    FAIL();  // There were no matches, so this code should never execute.
+    static_cast<void>(entry);
+  }
+}
+
+alignas(TokenDatabase::RawEntry) constexpr char kCollisionsData[] =
+    "TOKENS\0\0\x05\0\0\0\0\0\0\0"
+    "\x01\0\0\0date"
+    "\x01\0\0\0date"
+    "\x01\0\0\0date"
+    "\x02\0\0\0date"
+    "\xFF\0\0\0date"
+    "hi!\0goodbye\0:)\0\0";
+
+constexpr TokenDatabase kCollisions = TokenDatabase::Create<kCollisionsData>();
+static_assert(kCollisions.size() == 5u);
+
+TEST(TokenDatabase, MultipleEntriesWithSameToken) {
+  TokenDatabase::Entries match = kCollisions.Find(1);
+
+  EXPECT_EQ(match.begin()->token, 1u);
+  EXPECT_EQ(match.end()->token, 2u);
+  ASSERT_EQ(match.size(), 3u);
+
+  EXPECT_STREQ(match[0].string, "hi!");
+  EXPECT_STREQ(match[1].string, "goodbye");
+  EXPECT_STREQ(match[2].string, ":)");
+
+  for (const auto& entry : match) {
+    EXPECT_EQ(entry.token, 1u);
+  }
+}
+
+TEST(TokenDatabase, Empty) {
+  constexpr TokenDatabase empty_db = TokenDatabase::Create<kEmptyData>();
+  static_assert(empty_db.size() == 0u);
+  static_assert(empty_db.ok());
+
+  EXPECT_TRUE(empty_db.Find(0).empty());
+  EXPECT_TRUE(empty_db.Find(123).empty());
+
+  for (const auto& entry : empty_db) {
+    FAIL();  // The database is empty; this should never execute.
+    static_cast<void>(entry);
+  }
+}
+
+TEST(TokenDatabase, NullDatabase) {
+  constexpr TokenDatabase empty_db;
+
+  static_assert(empty_db.size() == 0u);
+  static_assert(!empty_db.ok());
+  EXPECT_TRUE(empty_db.Find(0).empty());
+}
+
+TEST(TokenDatabase, InvalidData) {
+  constexpr TokenDatabase bad_db = TokenDatabase::Create("TOKENS\0\0");
+
+  static_assert(!bad_db.ok());
+  EXPECT_TRUE(bad_db.Find(0).empty());
+}
+
+TEST(TokenDatabase, FromString) {
+  TokenDatabase bad_db = TokenDatabase::Create(std::string("wow!"));
+
+  EXPECT_FALSE(bad_db.ok());
+}
+
+}  // namespace
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/tokenize.cc b/pw_tokenizer/tokenize.cc
new file mode 100644
index 0000000..18bf88d
--- /dev/null
+++ b/pw_tokenizer/tokenize.cc
@@ -0,0 +1,261 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 defines the functions that encode tokenized logs at runtime. These
+// are the only pw_tokenizer functions present in a binary that tokenizes
+// strings. All other tokenizing code is resolved at compile time.
+
+#include "pw_tokenizer/tokenize.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdarg>
+#include <cstddef>
+#include <cstring>
+
+#include "pw_varint/varint.h"
+
+namespace pw::tokenizer {
+namespace {
+
+// Store metadata about this compilation's string tokenization in the ELF.
+//
+// The tokenizer metadata will not go into the on-device executable binary code.
+// This metadata will be present in the ELF file's .tokenizer_info section, from
+// which the host-side tooling (Python, Java, etc.) can understand how to decode
+// tokenized strings for the given binary. Only attributes that affect the
+// decoding process are recorded.
+//
+// Tokenizer metadata is stored in an array of key-value pairs. Each Metadata
+// object is 32 bytes: a 24-byte string and an 8-byte value. Metadata structs
+// may be parsed in Python with the struct format '24s<Q'.
+PW_PACKED(struct) Metadata {
+  char name[24];   // name of the metadata field
+  uint64_t value;  // value of the field
+};
+
+static_assert(sizeof(Metadata) == 32);
+
+// Store tokenization metadata in its own section.
+constexpr Metadata metadata[] PW_KEEP_IN_SECTION(".tokenzier_info") = {
+    {"hash_length_bytes", PW_TOKENIZER_CFG_HASH_LENGTH},
+    {"sizeof_long", sizeof(long)},            // %l conversion specifier
+    {"sizeof_intmax_t", sizeof(intmax_t)},    // %j conversion specifier
+    {"sizeof_size_t", sizeof(size_t)},        // %z conversion specifier
+    {"sizeof_ptrdiff_t", sizeof(ptrdiff_t)},  // %t conversion specifier
+};
+
+// Declare the types as an enum for convenience.
+enum class ArgType : uint8_t {
+  kInt = PW_TOKENIZER_ARG_TYPE_INT,
+  kInt64 = PW_TOKENIZER_ARG_TYPE_INT64,
+  kDouble = PW_TOKENIZER_ARG_TYPE_DOUBLE,
+  kString = PW_TOKENIZER_ARG_TYPE_STRING,
+};
+
+// Just to be safe, make sure these values are what we expect them to be.
+static_assert(0b00u == static_cast<uint8_t>(ArgType::kInt));
+static_assert(0b01u == static_cast<uint8_t>(ArgType::kInt64));
+static_assert(0b10u == static_cast<uint8_t>(ArgType::kDouble));
+static_assert(0b11u == static_cast<uint8_t>(ArgType::kString));
+
+// Buffer for encoding a tokenized string and arguments.
+struct EncodedMessage {
+  pw_TokenizerStringToken token;
+  std::array<uint8_t, PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES> args;
+};
+
+static_assert(offsetof(EncodedMessage, args) == sizeof(EncodedMessage::token),
+              "EncodedMessage should not have padding bytes between members");
+
+size_t EncodeInt(int value, const span<uint8_t>& output) {
+  return varint::Encode(value, pw::as_writable_bytes(output));
+}
+
+size_t EncodeInt64(int64_t value, const span<uint8_t>& output) {
+  return varint::Encode(value, pw::as_writable_bytes(output));
+}
+
+size_t EncodeFloat(float value, const span<uint8_t>& output) {
+  if (output.size() < sizeof(value)) {
+    return 0;
+  }
+  std::memcpy(output.data(), &value, sizeof(value));
+  return sizeof(value);
+}
+
+size_t EncodeString(const char* string, const span<uint8_t>& output) {
+  // The top bit of the status byte indicates if the string was truncated.
+  static constexpr size_t kMaxStringLength = 0x7Fu;
+
+  if (output.empty()) {  // At least one byte is needed for the status/size.
+    return 0;
+  }
+
+  if (string == nullptr) {
+    string = "NULL";
+  }
+
+  // Subtract 1 to save room for the status byte.
+  const size_t max_bytes = std::min(output.size(), kMaxStringLength) - 1;
+
+  // Scan the string to find out how many bytes to copy.
+  size_t bytes_to_copy = 0;
+  uint8_t overflow_bit = 0;
+
+  while (string[bytes_to_copy] != '\0') {
+    if (bytes_to_copy == max_bytes) {
+      overflow_bit = '\x80';
+      break;
+    }
+    bytes_to_copy += 1;
+  }
+
+  output[0] = bytes_to_copy | overflow_bit;
+  std::memcpy(output.data() + 1, string, bytes_to_copy);
+
+  return bytes_to_copy + 1;  // include the status byte in the total
+}
+
+size_t EncodeArgs(pw_TokenizerArgTypes types,
+                  va_list args,
+                  span<uint8_t> output) {
+  size_t arg_count = types & PW_TOKENIZER_TYPE_COUNT_MASK;
+  types >>= PW_TOKENIZER_TYPE_COUNT_SIZE_BITS;
+
+  size_t encoded_bytes = 0;
+  while (arg_count != 0u) {
+    // How many bytes were encoded; 0 indicates that there wasn't enough space.
+    size_t argument_bytes = 0;
+
+    switch (static_cast<ArgType>(types & 0b11u)) {
+      case ArgType::kInt:
+        argument_bytes = EncodeInt(va_arg(args, int), output);
+        break;
+      case ArgType::kInt64:
+        argument_bytes = EncodeInt64(va_arg(args, int64_t), output);
+        break;
+      case ArgType::kDouble:
+        argument_bytes =
+            EncodeFloat(static_cast<float>(va_arg(args, double)), output);
+        break;
+      case ArgType::kString:
+        argument_bytes = EncodeString(va_arg(args, const char*), output);
+        break;
+    }
+
+    // If zero bytes were encoded, the encoding buffer is full.
+    if (argument_bytes == 0u) {
+      break;
+    }
+
+    output = output.subspan(argument_bytes);
+    encoded_bytes += argument_bytes;
+
+    arg_count -= 1;
+    types >>= 2;  // each argument type is encoded in two bits
+  }
+
+  return encoded_bytes;
+}
+
+}  // namespace
+
+extern "C" {
+
+void pw_TokenizeToBuffer(void* buffer,
+                         size_t* buffer_size_bytes,
+                         pw_TokenizerStringToken token,
+                         pw_TokenizerArgTypes types,
+                         ...) {
+  if (*buffer_size_bytes < sizeof(token)) {
+    *buffer_size_bytes = 0;
+    return;
+  }
+
+  std::memcpy(buffer, &token, sizeof(token));
+
+  va_list args;
+  va_start(args, types);
+  const size_t encoded_bytes =
+      EncodeArgs(types,
+                 args,
+                 span(static_cast<uint8_t*>(buffer) + sizeof(token),
+                      *buffer_size_bytes - sizeof(token)));
+  va_end(args);
+
+  *buffer_size_bytes = sizeof(token) + encoded_bytes;
+}
+
+void pw_TokenizeToCallback(void (*callback)(const uint8_t* encoded_message,
+                                            size_t size_bytes),
+                           pw_TokenizerStringToken token,
+                           pw_TokenizerArgTypes types,
+                           ...) {
+  EncodedMessage encoded;
+  encoded.token = token;
+
+  va_list args;
+  va_start(args, types);
+  const size_t encoded_bytes = EncodeArgs(types, args, encoded.args);
+  va_end(args);
+
+  callback(reinterpret_cast<const uint8_t*>(&encoded),
+           sizeof(encoded.token) + encoded_bytes);
+}
+
+#if PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+void pw_TokenizeToGlobalHandler(pw_TokenizerStringToken token,
+                                pw_TokenizerArgTypes types,
+                                ...) {
+  EncodedMessage encoded;
+  encoded.token = token;
+
+  va_list args;
+  va_start(args, types);
+  const size_t encoded_bytes = EncodeArgs(types, args, encoded.args);
+  va_end(args);
+
+  pw_TokenizerHandleEncodedMessage(reinterpret_cast<const uint8_t*>(&encoded),
+                                   sizeof(encoded.token) + encoded_bytes);
+}
+
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+#if PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+
+void pw_TokenizeToGlobalHandlerWithPayload(const pw_TokenizerPayload payload,
+                                           pw_TokenizerStringToken token,
+                                           pw_TokenizerArgTypes types,
+                                           ...) {
+  EncodedMessage encoded;
+  encoded.token = token;
+
+  va_list args;
+  va_start(args, types);
+  const size_t encoded_bytes = EncodeArgs(types, args, encoded.args);
+  va_end(args);
+
+  pw_TokenizerHandleEncodedMessageWithPayload(
+      payload,
+      reinterpret_cast<const uint8_t*>(&encoded),
+      sizeof(encoded.token) + encoded_bytes);
+}
+
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+
+}  // extern "C"
+
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/tokenize_test.c b/pw_tokenizer/tokenize_test.c
new file mode 100644
index 0000000..1574451
--- /dev/null
+++ b/pw_tokenizer/tokenize_test.c
@@ -0,0 +1,124 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 function tests the C implementation of tokenization API. These functions
+// are called from the main C++ test file.
+
+#include "pw_tokenizer_private/tokenize_test.h"
+
+#include "pw_tokenizer/tokenize.h"
+
+#ifdef __cplusplus
+#error "This is a test of C code and must be compiled as C, not C++."
+#endif  // __cplusplus
+
+void pw_TokenizeToBufferTest_StringShortFloat(void* buffer,
+                                              size_t* buffer_size) {
+  char str[] = "1";
+  PW_TOKENIZE_TO_BUFFER(
+      buffer, buffer_size, TEST_FORMAT_STRING_SHORT_FLOAT, str, (short)-2, 3.0);
+}
+
+// This test invokes the tokenization API with a variety of types. To simplify
+// validating the encoded data, numbers that are sequential when zig-zag encoded
+// are used as arguments.
+void pw_TokenizeToBufferTest_SequentialZigZag(void* buffer,
+                                              size_t* buffer_size) {
+  PW_TOKENIZE_TO_BUFFER(buffer,
+                        buffer_size,
+                        TEST_FORMAT_SEQUENTIAL_ZIG_ZAG,
+                        0u,
+                        -1,
+                        1u,
+                        (unsigned)-2,
+                        (unsigned short)2u,
+                        (signed char)-3,
+                        3,
+                        -4l,
+                        4ul,
+                        -5ll,
+                        5ull,
+                        (signed char)-6,
+                        (char)6,
+                        (signed char)-7);
+}
+
+void pw_TokenizeToCallbackTest_SequentialZigZag(
+    void (*callback)(const uint8_t* buffer, size_t size)) {
+  PW_TOKENIZE_TO_CALLBACK(callback,
+                          TEST_FORMAT_SEQUENTIAL_ZIG_ZAG,
+                          0u,
+                          -1,
+                          1u,
+                          (unsigned)-2,
+                          (unsigned short)2u,
+                          (signed char)-3,
+                          3,
+                          -4l,
+                          4ul,
+                          -5ll,
+                          5ull,
+                          (signed char)-6,
+                          (char)6,
+                          (signed char)-7);
+}
+
+#if PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+void pw_TokenizeToGlobalHandlerTest_SequentialZigZag(void) {
+  PW_TOKENIZE_TO_GLOBAL_HANDLER(TEST_FORMAT_SEQUENTIAL_ZIG_ZAG,
+                                0u,
+                                -1,
+                                1u,
+                                (unsigned)-2,
+                                (unsigned short)2u,
+                                (signed char)-3,
+                                3,
+                                -4l,
+                                4ul,
+                                -5ll,
+                                5ull,
+                                (signed char)-6,
+                                (char)6,
+                                (signed char)-7);
+}
+
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+#if PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+
+void pw_TokenizeToGlobalHandlerWithPayloadTest_SequentialZigZag(void) {
+  PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD((pw_TokenizerPayload)600613,
+                                             TEST_FORMAT_SEQUENTIAL_ZIG_ZAG,
+                                             0u,
+                                             -1,
+                                             1u,
+                                             (unsigned)-2,
+                                             (unsigned short)2u,
+                                             (signed char)-3,
+                                             3,
+                                             -4l,
+                                             4ul,
+                                             -5ll,
+                                             5ull,
+                                             (signed char)-6,
+                                             (char)6,
+                                             (signed char)-7);
+}
+
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+
+void pw_TokenizeToBufferTest_Requires8(void* buffer, size_t* buffer_size) {
+  PW_TOKENIZE_TO_BUFFER(buffer, buffer_size, TEST_FORMAT_REQUIRES_8, "hi", -7);
+}
diff --git a/pw_tokenizer/tokenize_test.cc b/pw_tokenizer/tokenize_test.cc
new file mode 100644
index 0000000..73ffaab
--- /dev/null
+++ b/pw_tokenizer/tokenize_test.cc
@@ -0,0 +1,542 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     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 "pw_tokenizer/tokenize.h"
+
+#include <cinttypes>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+
+#include "gtest/gtest.h"
+#include "pw_tokenizer/pw_tokenizer_65599_fixed_length_hash.h"
+#include "pw_tokenizer_private/tokenize_test.h"
+#include "pw_varint/varint.h"
+
+namespace pw::tokenizer {
+namespace {
+
+// The hash to use for this test. This makes sure the strings are shorter than
+// the configured max length to ensure this test works with any reasonable
+// configuration.
+template <size_t kSize>
+constexpr uint32_t TestHash(const char (&string)[kSize]) {
+  constexpr unsigned kTestHashLength = 48;
+  static_assert(kTestHashLength <= PW_TOKENIZER_CFG_HASH_LENGTH);
+  static_assert(kSize <= kTestHashLength + 1);
+  return PwTokenizer65599FixedLengthHash(std::string_view(string, kSize - 1),
+                                         kTestHashLength);
+}
+
+// Constructs an array with the hashed string followed by the provided bytes.
+template <uint8_t... kData, size_t kSize>
+constexpr auto ExpectedData(const char (&format)[kSize]) {
+  const uint32_t value = TestHash(format);
+  return std::array<uint8_t, sizeof(uint32_t) + sizeof...(kData)>{
+      static_cast<uint8_t>(value & 0xff),
+      static_cast<uint8_t>(value >> 8 & 0xff),
+      static_cast<uint8_t>(value >> 16 & 0xff),
+      static_cast<uint8_t>(value >> 24 & 0xff),
+      kData...};
+}
+
+TEST(TokenizeStringLiteral, EmptyString_IsZero) {
+  constexpr pw_TokenizerStringToken token = PW_TOKENIZE_STRING("");
+  EXPECT_EQ(0u, token);
+}
+
+TEST(TokenizeStringLiteral, String_MatchesHash) {
+  constexpr uint32_t token = PW_TOKENIZE_STRING("[:-)");
+  EXPECT_EQ(TestHash("[:-)"), token);
+}
+
+constexpr uint32_t kGlobalToken = PW_TOKENIZE_STRING(">:-[]");
+
+TEST(TokenizeStringLiteral, GlobalVariable_MatchesHash) {
+  EXPECT_EQ(TestHash(">:-[]"), kGlobalToken);
+}
+
+class TokenizeToBuffer : public ::testing::Test {
+ public:
+  TokenizeToBuffer() : buffer_{} {}
+
+ protected:
+  uint8_t buffer_[64];
+};
+
+TEST_F(TokenizeToBuffer, Integer64) {
+  size_t message_size = 14;
+  PW_TOKENIZE_TO_BUFFER(
+      buffer_,
+      &message_size,
+      "%" PRIu64,
+      static_cast<uint64_t>(0x55555555'55555555ull));  // 0xAAAAAAAA'AAAAAAAA
+
+  // Pattern becomes 10101010'11010101'10101010 ...
+  constexpr std::array<uint8_t, 14> expected =
+      ExpectedData<0xAA, 0xD5, 0xAA, 0xD5, 0xAA, 0xD5, 0xAA, 0xD5, 0xAA, 0x01>(
+          "%" PRIu64);
+  ASSERT_EQ(expected.size(), message_size);
+  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, Integer64Overflow) {
+  size_t message_size;
+
+  for (size_t size = 4; size < 20; ++size) {
+    message_size = size;
+
+    PW_TOKENIZE_TO_BUFFER(
+        buffer_,
+        &message_size,
+        "%" PRIx64,
+        static_cast<uint64_t>(std::numeric_limits<int64_t>::min()));
+
+    if (size < 14) {
+      constexpr std::array<uint8_t, 4> empty = ExpectedData("%" PRIx64);
+      ASSERT_EQ(sizeof(uint32_t), message_size);
+      EXPECT_EQ(std::memcmp(empty.data(), &buffer_, empty.size()), 0);
+
+      // Make sure nothing was written past the end of the buffer.
+      EXPECT_TRUE(std::all_of(&buffer_[size], std::end(buffer_), [](uint8_t v) {
+        return v == '\0';
+      }));
+    } else {
+      constexpr std::array<uint8_t, 14> expected =
+          ExpectedData<0xff,
+                       0xff,
+                       0xff,
+                       0xff,
+                       0xff,
+                       0xff,
+                       0xff,
+                       0xff,
+                       0xff,
+                       0x01>("%" PRIx64);
+      ASSERT_EQ(expected.size(), message_size);
+      EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+    }
+  }
+}
+
+TEST_F(TokenizeToBuffer, IntegerNegative) {
+  size_t message_size = 9;
+  PW_TOKENIZE_TO_BUFFER(
+      buffer_, &message_size, "%" PRId32, std::numeric_limits<int32_t>::min());
+
+  // 0x8000'0000 -zig-zag-> 0xff'ff'ff'ff'0f
+  constexpr std::array<uint8_t, 9> expected =
+      ExpectedData<0xff, 0xff, 0xff, 0xff, 0x0f>("%" PRId32);
+  ASSERT_EQ(expected.size(), message_size);
+  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, IntegerMin) {
+  size_t message_size = 9;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "%d", -1);
+
+  constexpr std::array<uint8_t, 5> expected = ExpectedData<0x01>("%d");
+  ASSERT_EQ(expected.size(), message_size);
+  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, IntegerDoesntFit) {
+  size_t message_size = 8;
+  PW_TOKENIZE_TO_BUFFER(
+      buffer_, &message_size, "%" PRId32, std::numeric_limits<int32_t>::min());
+
+  constexpr std::array<uint8_t, 4> expected = ExpectedData<>("%" PRId32);
+  ASSERT_EQ(expected.size(), message_size);
+  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, String) {
+  size_t message_size = sizeof(buffer_);
+
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer is: %s", "5432!");
+  constexpr std::array<uint8_t, 10> expected =
+      ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s");
+
+  ASSERT_EQ(expected.size(), message_size);
+  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, String_BufferTooSmall_TruncatesAndSetsTopStatusBit) {
+  size_t message_size = 8;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer is: %s", "5432!");
+
+  constexpr std::array<uint8_t, 8> truncated_1 =
+      ExpectedData<0x83, '5', '4', '3'>("The answer is: %s");
+
+  ASSERT_EQ(truncated_1.size(), message_size);
+  EXPECT_EQ(std::memcmp(truncated_1.data(), buffer_, truncated_1.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, String_TwoBytesLeft_TruncatesToOneCharacter) {
+  size_t message_size = 6;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer is: %s", "5432!");
+
+  constexpr std::array<uint8_t, 6> truncated_2 =
+      ExpectedData<0x81, '5'>("The answer is: %s");
+
+  ASSERT_EQ(truncated_2.size(), message_size);
+  EXPECT_EQ(std::memcmp(truncated_2.data(), buffer_, truncated_2.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, String_OneByteLeft_OnlyWritesTruncatedStatusByte) {
+  size_t message_size = 5;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer is: %s", "5432!");
+
+  std::array<uint8_t, 5> result = ExpectedData<0x80>("The answer is: %s");
+  ASSERT_EQ(result.size(), message_size);
+  EXPECT_EQ(std::memcmp(result.data(), buffer_, result.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, EmptyString_OneByteLeft_EncodesCorrectly) {
+  size_t message_size = 5;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer is: %s", "");
+
+  std::array<uint8_t, 5> result = ExpectedData<0>("The answer is: %s");
+  ASSERT_EQ(result.size(), message_size);
+  EXPECT_EQ(std::memcmp(result.data(), buffer_, result.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, String_ZeroBytesLeft_WritesNothing) {
+  size_t message_size = 4;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer is: %s", "5432!");
+
+  constexpr std::array<uint8_t, 4> empty = ExpectedData<>("The answer is: %s");
+  ASSERT_EQ(empty.size(), message_size);
+  EXPECT_EQ(std::memcmp(empty.data(), buffer_, empty.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, NullptrString_EncodesNull) {
+  char* string = nullptr;
+  size_t message_size = 9;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer is: %s", string);
+
+  std::array<uint8_t, 9> result =
+      ExpectedData<4, 'N', 'U', 'L', 'L'>("The answer is: %s");
+  ASSERT_EQ(result.size(), message_size);
+  EXPECT_EQ(std::memcmp(result.data(), buffer_, result.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, NullptrString_BufferTooSmall_EncodesTruncatedNull) {
+  char* string = nullptr;
+  size_t message_size = 6;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer is: %s", string);
+
+  std::array<uint8_t, 6> result = ExpectedData<0x81, 'N'>("The answer is: %s");
+  ASSERT_EQ(result.size(), message_size);
+  EXPECT_EQ(std::memcmp(result.data(), buffer_, result.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, TruncateArgs) {
+  // Args that can't fit are dropped completely
+  size_t message_size = 6;
+  PW_TOKENIZE_TO_BUFFER(buffer_,
+                        &message_size,
+                        "%u %d",
+                        static_cast<uint8_t>(0b0010'1010u),
+                        0xffffff);
+
+  constexpr std::array<uint8_t, 5> expected =
+      ExpectedData<0b0101'0100u>("%u %d");
+  ASSERT_EQ(expected.size(), message_size);
+  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, NoRoomForToken) {
+  // Nothing is written if there isn't room for the token.
+  std::memset(buffer_, '$', sizeof(buffer_));
+  auto is_untouched = [](uint8_t v) { return v == '$'; };
+
+  size_t message_size = 3;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "The answer: \"%s\"", "5432!");
+  EXPECT_EQ(0u, message_size);
+  EXPECT_TRUE(std::all_of(buffer_, std::end(buffer_), is_untouched));
+
+  message_size = 2;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "Jello, world!");
+  EXPECT_EQ(0u, message_size);
+  EXPECT_TRUE(std::all_of(buffer_, std::end(buffer_), is_untouched));
+
+  message_size = 1;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "Jello!");
+  EXPECT_EQ(0u, message_size);
+  EXPECT_TRUE(std::all_of(buffer_, std::end(buffer_), is_untouched));
+
+  message_size = 0;
+  PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, "Jello?");
+  EXPECT_EQ(0u, message_size);
+  EXPECT_TRUE(std::all_of(buffer_, std::end(buffer_), is_untouched));
+}
+
+TEST_F(TokenizeToBuffer, C_StringShortFloat) {
+  size_t size = sizeof(buffer_);
+  pw_TokenizeToBufferTest_StringShortFloat(buffer_, &size);
+  constexpr std::array<uint8_t, 11> expected =  // clang-format off
+      ExpectedData<1, '1',                 // string '1'
+                   3,                      // -2 (zig-zag encoded)
+                   0x00, 0x00, 0x40, 0x40  // 3.0 in floating point
+                   >(TEST_FORMAT_STRING_SHORT_FLOAT);
+  ASSERT_EQ(expected.size(), size);  // clang-format on
+  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, C_SequentialZigZag) {
+  size_t size = sizeof(buffer_);
+  pw_TokenizeToBufferTest_SequentialZigZag(buffer_, &size);
+  constexpr std::array<uint8_t, 18> expected =
+      ExpectedData<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13>(
+          TEST_FORMAT_SEQUENTIAL_ZIG_ZAG);
+
+  ASSERT_EQ(expected.size(), size);
+  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToBuffer, C_Overflow) {
+  std::memset(buffer_, '$', sizeof(buffer_));
+
+  {
+    size_t size = 7;
+    pw_TokenizeToBufferTest_Requires8(buffer_, &size);
+    constexpr std::array<uint8_t, 7> expected =
+        ExpectedData<2, 'h', 'i'>(TEST_FORMAT_REQUIRES_8);
+    ASSERT_EQ(expected.size(), size);
+    EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+    EXPECT_EQ(buffer_[7], '$');
+  }
+
+  {
+    size_t size = 8;
+    pw_TokenizeToBufferTest_Requires8(buffer_, &size);
+    constexpr std::array<uint8_t, 8> expected =
+        ExpectedData<2, 'h', 'i', 13>(TEST_FORMAT_REQUIRES_8);
+    ASSERT_EQ(expected.size(), size);
+    EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
+    EXPECT_EQ(buffer_[8], '$');
+  }
+}
+
+// Test fixture for callback and global handler. Both of these need a global
+// message buffer. To keep the message buffers separate, template this on the
+// derived class type.
+template <typename Impl>
+class GlobalMessage : public ::testing::Test {
+ public:
+  static void SetMessage(const uint8_t* message, size_t size) {
+    ASSERT_LE(size, sizeof(message_));
+    std::memcpy(message_, message, size);
+    message_size_bytes_ = size;
+  }
+
+ protected:
+  GlobalMessage() {
+    std::memset(message_, 0, sizeof(message_));
+    message_size_bytes_ = 0;
+  }
+
+  static uint8_t message_[256];
+  static size_t message_size_bytes_;
+};
+
+template <typename Impl>
+uint8_t GlobalMessage<Impl>::message_[256] = {};
+template <typename Impl>
+size_t GlobalMessage<Impl>::message_size_bytes_ = 0;
+
+class TokenizeToCallback : public GlobalMessage<TokenizeToCallback> {};
+
+TEST_F(TokenizeToCallback, Variety) {
+  PW_TOKENIZE_TO_CALLBACK(
+      SetMessage, "%s there are %x (%.2f) of them%c", "Now", 2u, 2.0f, '.');
+  const auto expected =  // clang-format off
+      ExpectedData<3, 'N', 'o', 'w',        // string "Now"
+                   0x04,                    // unsigned 2 (zig-zag encoded)
+                   0x00, 0x00, 0x00, 0x40,  // float 2.0
+                   0x5C                     // char '.' (0x2E, zig-zag encoded)
+                   >("%s there are %x (%.2f) of them%c");
+  // clang-format on
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToCallback, Strings) {
+  PW_TOKENIZE_TO_CALLBACK(SetMessage, "The answer is: %s", "5432!");
+  constexpr std::array<uint8_t, 10> expected =
+      ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s");
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToCallback, C_SequentialZigZag) {
+  pw_TokenizeToCallbackTest_SequentialZigZag(SetMessage);
+
+  constexpr std::array<uint8_t, 18> expected =
+      ExpectedData<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13>(
+          TEST_FORMAT_SEQUENTIAL_ZIG_ZAG);
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+}
+
+#if PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+class TokenizeToGlobalHandler : public GlobalMessage<TokenizeToGlobalHandler> {
+};
+
+TEST_F(TokenizeToGlobalHandler, Variety) {
+  PW_TOKENIZE_TO_GLOBAL_HANDLER("%x%lld%1.2f%s", 0, 0ll, -0.0, "");
+  const auto expected =
+      ExpectedData<0, 0, 0x00, 0x00, 0x00, 0x80, 0>("%x%lld%1.2f%s");
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToGlobalHandler, Strings) {
+  PW_TOKENIZE_TO_GLOBAL_HANDLER("The answer is: %s", "5432!");
+  constexpr std::array<uint8_t, 10> expected =
+      ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s");
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+}
+
+TEST_F(TokenizeToGlobalHandler, C_SequentialZigZag) {
+  pw_TokenizeToGlobalHandlerTest_SequentialZigZag();
+
+  constexpr std::array<uint8_t, 18> expected =
+      ExpectedData<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13>(
+          TEST_FORMAT_SEQUENTIAL_ZIG_ZAG);
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+}
+
+extern "C" void pw_TokenizerHandleEncodedMessage(const uint8_t* encoded_message,
+                                                 size_t size_bytes) {
+  TokenizeToGlobalHandler::SetMessage(encoded_message, size_bytes);
+}
+
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER
+
+#if PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+
+class TokenizeToGlobalHandlerWithPayload
+    : public GlobalMessage<TokenizeToGlobalHandlerWithPayload> {
+ public:
+  static void SetPayload(pw_TokenizerPayload payload) {
+    payload_ = reinterpret_cast<intptr_t>(payload);
+  }
+
+ protected:
+  TokenizeToGlobalHandlerWithPayload() { payload_ = {}; }
+
+  static intptr_t payload_;
+};
+
+intptr_t TokenizeToGlobalHandlerWithPayload::payload_;
+
+TEST_F(TokenizeToGlobalHandlerWithPayload, Variety) {
+  ASSERT_NE(payload_, 123);
+
+  const auto expected =
+      ExpectedData<0, 0, 0x00, 0x00, 0x00, 0x80, 0>("%x%lld%1.2f%s");
+
+  PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
+      reinterpret_cast<pw_TokenizerPayload>(123),
+      "%x%lld%1.2f%s",
+      0,
+      0ll,
+      -0.0,
+      "");
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+  EXPECT_EQ(payload_, 123);
+
+  PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
+      reinterpret_cast<pw_TokenizerPayload>(-543),
+      "%x%lld%1.2f%s",
+      0,
+      0ll,
+      -0.0,
+      "");
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+  EXPECT_EQ(payload_, -543);
+}
+
+TEST_F(TokenizeToGlobalHandlerWithPayload, Strings) {
+  constexpr std::array<uint8_t, 10> expected =
+      ExpectedData<5, '5', '4', '3', '2', '!'>("The answer is: %s");
+
+  PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
+      reinterpret_cast<pw_TokenizerPayload>(5432),
+      "The answer is: %s",
+      "5432!");
+
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+  EXPECT_EQ(payload_, 5432);
+
+  PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD({}, "The answer is: %s", "5432!");
+
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+  EXPECT_EQ(payload_, 0);
+}
+
+struct Foo {
+  unsigned char a;
+  bool b;
+};
+
+TEST_F(TokenizeToGlobalHandlerWithPayload, PointerToStack) {
+  Foo foo{254u, true};
+
+  PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
+      reinterpret_cast<pw_TokenizerPayload>(&foo), "Boring!");
+
+  constexpr auto expected = ExpectedData("Boring!");
+  static_assert(expected.size() == 4);
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+
+  Foo* payload_foo = reinterpret_cast<Foo*>(payload_);
+  ASSERT_EQ(&foo, payload_foo);
+  EXPECT_EQ(payload_foo->a, 254u);
+  EXPECT_TRUE(payload_foo->b);
+}
+
+TEST_F(TokenizeToGlobalHandlerWithPayload, C_SequentialZigZag) {
+  pw_TokenizeToGlobalHandlerWithPayloadTest_SequentialZigZag();
+
+  constexpr std::array<uint8_t, 18> expected =
+      ExpectedData<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13>(
+          TEST_FORMAT_SEQUENTIAL_ZIG_ZAG);
+  ASSERT_EQ(expected.size(), message_size_bytes_);
+  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+  EXPECT_EQ(payload_, 600613);
+}
+
+extern "C" void pw_TokenizerHandleEncodedMessageWithPayload(
+    pw_TokenizerPayload payload,
+    const uint8_t* encoded_message,
+    size_t size_bytes) {
+  TokenizeToGlobalHandlerWithPayload::SetMessage(encoded_message, size_bytes);
+  TokenizeToGlobalHandlerWithPayload::SetPayload(payload);
+}
+
+#endif  // PW_TOKENIZER_CFG_ENABLE_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD
+
+}  // namespace
+}  // namespace pw::tokenizer
diff --git a/pw_tokenizer/tokenizer_linker_sections.ld b/pw_tokenizer/tokenizer_linker_sections.ld
new file mode 100644
index 0000000..db08481
--- /dev/null
+++ b/pw_tokenizer/tokenizer_linker_sections.ld
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 The Pigweed Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *     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 linker script snippet declares the sections needed for string
+ * tokenization.
+ *
+ * This file may be directly included in a linker script with an include
+ * directive. For example,
+ *
+ *   INCLUDE path/to/modules/pw_tokenizer/tokenizer_linker_sections.ld
+ *
+ *   SECTIONS
+ *   {
+ *     (your existing linker sections)
+ *   }
+ */
+
+SECTIONS
+{
+  /*
+   * All tokenized strings are stored in this section. Since the section has
+   * type INFO, it is excluded from the final binary.
+   *
+   * In the compiled code, format string literals are replaced by a hash of the
+   * string contents and a compact argument list encoded in a uint32_t. The
+   * compiled code contains no references to the tokenized strings in this
+   * section.
+   *
+   * The section contents are declared with KEEP so that they are not removed
+   * from the ELF. These are never emitted in the final binary or loaded into
+   * memory.
+   */
+  .tokenized 0x00000000 (INFO) :
+  {
+    KEEP(*(.tokenized))
+    KEEP(*(.tokenized.*))
+  }
+
+  /*
+   * This section stores metadata that may be used during tokenized string
+   * decoding. This metadata describes properties that may affect how the
+   * tokenized string is encoded or decoded -- the maximum length of the hash
+   * function and the sizes of certain integer types.
+   *
+   * Metadata is declared as key-value pairs. See the metadata variable in
+   * tokenize.cc for further details.
+   */
+  .tokenizer_info 0x00000000 (INFO) :
+  {
+    KEEP(*(.tokenizer_info))
+  }
+}