[NFC] Use a tree structure in list_cpu_features

This is in preparation to include cache hierarchy in the dumped data.
diff --git a/src/utils/list_cpu_features.c b/src/utils/list_cpu_features.c
index acda5e7..41fc612 100644
--- a/src/utils/list_cpu_features.c
+++ b/src/utils/list_cpu_features.c
@@ -12,6 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// This program dumps current host data to the standard output.
+// Output can be text or json if the `--json` flag is passed.
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -30,7 +36,176 @@
 #include "cpuinfo_ppc.h"
 #endif
 
-static void PrintEscapedAscii(const char* str) {
+// Design principles
+// -----------------
+// We build a tree structure containing all the data to be displayed.
+// Then depending on the output type (text or json) we walk the tree and display
+// the data accordingly.
+
+// We use a bump allocator to allocate strings and nodes of the tree,
+// Memory is not intented to be reclaimed.
+typedef struct {
+  char* ptr;
+  size_t size;
+} BumpAllocator;
+
+// Allocate a buffer of size `size`.
+static BumpAllocator BA_Create(size_t size) {
+  char* const ptr = (char*)malloc(size);
+  BumpAllocator BA;
+  if (ptr) BA = (BumpAllocator){.ptr = ptr, .size = size};
+  return BA;
+}
+
+// Update the available memory left in the BumpAllocator.
+static void* BA_Bump(BumpAllocator* BA, size_t size) {
+  assert(BA->size >= size);
+  void* ptr = BA->ptr;
+  BA->size -= size;
+  BA->ptr += size;
+  return ptr;
+}
+
+// The type of the nodes in the tree.
+typedef enum {
+  TNT_INVALID,
+  TNT_INT,
+  TNT_MAP,
+  TNT_MAP_ENTRY,
+  TNT_ARRAY,
+  TNT_ARRAY_ELEMENT,
+  TNT_STRING,
+} TreeValueType;
+
+// The node in the tree.
+typedef struct TreeValue {
+  TreeValueType type;
+  unsigned integer;
+  const char* string;
+  struct TreeValue* value;
+  struct TreeValue* next;
+} TreeValue;
+
+// Allocates a node inside a BumpAllocator.
+static TreeValue* BA_TreeValue(BumpAllocator* BA, TreeValueType type) {
+  TreeValue* TV = (TreeValue*)BA_Bump(BA, sizeof(TreeValue));
+  assert(TV);
+  TV->type = type;
+  return TV;
+}
+
+// Allocates an integer node inside a BumpAllocator.
+static TreeValue* CreateInt(BumpAllocator* BA, int value) {
+  TreeValue* TV = BA_TreeValue(BA, TNT_INT);
+  TV->integer = value;
+  return TV;
+}
+
+// Allocates a string node inside a BumpAllocator.
+// `value` must outlive the tree.
+static TreeValue* CreateConstantString(BumpAllocator* BA, const char* value) {
+  TreeValue* TV = BA_TreeValue(BA, TNT_STRING);
+  TV->string = value;
+  return TV;
+}
+
+// Allocates a map node inside a BumpAllocator.
+static TreeValue* CreateMap(BumpAllocator* BA) {
+  TreeValue* TV = BA_TreeValue(BA, TNT_MAP);
+  TV->next = NULL;
+  return TV;
+}
+
+// Allocates an array node inside a BumpAllocator.
+static TreeValue* CreateArray(BumpAllocator* BA) {
+  TreeValue* TV = BA_TreeValue(BA, TNT_ARRAY);
+  TV->next = NULL;
+  return TV;
+}
+
+// Allocates a formatted string inside a BumpAllocator.
+static TreeValue* CreatePrintfString(BumpAllocator* BA, const char* format,
+                                     ...) {
+  va_list arglist;
+  va_start(arglist, format);
+  char* const ptr = BA->ptr;
+  const int written = vsnprintf(ptr, BA->size, format, arglist);
+  va_end(arglist);
+  if (written < 0 || written >= BA->size) return NULL;
+  return CreateConstantString(BA, (char*)BA_Bump(BA, written));
+}
+
+static TreeValue* CreateString(BumpAllocator* BA, const char* value) {
+  return CreatePrintfString(BA, "%s", value);
+}
+
+// Allocates a map entry node inside a BumpAllocator.
+static void AddMapEntry(BumpAllocator* BA, TreeValue* map, const char* key,
+                        TreeValue* value) {
+  assert(map && map->type == TNT_MAP);
+  TreeValue* current = map;
+  while (current->next) current = current->next;
+  current->next = (TreeValue*)BA_Bump(BA, sizeof(TreeValue));
+  current->next->type = TNT_MAP_ENTRY;
+  current->next->string = key;
+  current->next->value = value;
+  current->next->next = NULL;
+}
+
+// Allocates aan array element node inside a BumpAllocator.
+static void AddArrayElement(BumpAllocator* BA, TreeValue* array,
+                            TreeValue* value) {
+  assert(array && array->type == TNT_ARRAY);
+  TreeValue* current = array;
+  while (current->next) current = current->next;
+  current->next = (TreeValue*)BA_Bump(BA, sizeof(TreeValue));
+  current->next->type = TNT_ARRAY_ELEMENT;
+  current->next->value = value;
+  current->next->next = NULL;
+}
+
+static int cmp(const void* p1, const void* p2) {
+  return strcmp(*(const char* const*)p1, *(const char* const*)p2);
+}
+
+#define DEFINE_ADD_FLAGS(HasFeature, FeatureName, FeatureType, LastEnum) \
+  static void AddFlags(BumpAllocator* BA, TreeValue* map,                \
+                       const FeatureType* features) {                    \
+    size_t i;                                                            \
+    const char* ptrs[LastEnum] = {0};                                    \
+    size_t count = 0;                                                    \
+    for (i = 0; i < LastEnum; ++i) {                                     \
+      if (HasFeature(features, i)) {                                     \
+        ptrs[count] = FeatureName(i);                                    \
+        ++count;                                                         \
+      }                                                                  \
+    }                                                                    \
+    qsort((void*)ptrs, count, sizeof(char*), cmp);                       \
+    TreeValue* const array = CreateArray(BA);                            \
+    for (i = 0; i < count; ++i)                                          \
+      AddArrayElement(BA, array, CreateConstantString(BA, ptrs[i]));     \
+    AddMapEntry(BA, map, "flags", array);                                \
+  }
+
+#if defined(CPU_FEATURES_ARCH_X86)
+DEFINE_ADD_FLAGS(GetX86FeaturesEnumValue, GetX86FeaturesEnumName, X86Features,
+                 X86_LAST_)
+#elif defined(CPU_FEATURES_ARCH_ARM)
+DEFINE_ADD_FLAGS(GetArmFeaturesEnumValue, GetArmFeaturesEnumName, ArmFeatures,
+                 ARM_LAST_)
+#elif defined(CPU_FEATURES_ARCH_AARCH64)
+DEFINE_ADD_FLAGS(GetAarch64FeaturesEnumValue, GetAarch64FeaturesEnumName,
+                 Aarch64Features, AARCH64_LAST_)
+#elif defined(CPU_FEATURES_ARCH_MIPS)
+DEFINE_ADD_FLAGS(GetMipsFeaturesEnumValue, GetMipsFeaturesEnumName,
+                 MipsFeatures, MIPS_LAST_)
+#elif defined(CPU_FEATURES_ARCH_PPC)
+DEFINE_ADD_FLAGS(GetPPCFeaturesEnumValue, GetPPCFeaturesEnumName, PPCFeatures,
+                 PPC_LAST_)
+#endif
+
+// Prints a json string with characters escaping.
+static void printJsonString(const char* str) {
   putchar('"');
   for (; str && *str; ++str) {
     switch (*str) {
@@ -49,170 +224,80 @@
   putchar('"');
 }
 
-static void PrintVoid(void) {}
-static void PrintComma(void) { putchar(','); }
-static void PrintLineFeed(void) { putchar('\n'); }
-static void PrintOpenBrace(void) { putchar('{'); }
-static void PrintCloseBrace(void) { putchar('}'); }
-static void PrintOpenBracket(void) { putchar('['); }
-static void PrintCloseBracket(void) { putchar(']'); }
-static void PrintString(const char* field) { printf("%s", field); }
-static void PrintAlignedHeader(const char* field) { printf("%-15s : ", field); }
-static void PrintIntValue(int value) { printf("%d", value); }
-static void PrintDecHexValue(int value) {
-  printf("%3d (0x%02X)", value, value);
-}
-static void PrintJsonHeader(const char* field) {
-  PrintEscapedAscii(field);
-  putchar(':');
-}
-
-typedef struct {
-  void (*Start)(void);
-  void (*ArrayStart)(void);
-  void (*ArraySeparator)(void);
-  void (*ArrayEnd)(void);
-  void (*PrintString)(const char* value);
-  void (*PrintValue)(int value);
-  void (*EndField)(void);
-  void (*StartField)(const char* field);
-  void (*End)(void);
-} Printer;
-
-static Printer getJsonPrinter(void) {
-  return (Printer){
-      .Start = &PrintOpenBrace,
-      .ArrayStart = &PrintOpenBracket,
-      .ArraySeparator = &PrintComma,
-      .ArrayEnd = &PrintCloseBracket,
-      .PrintString = &PrintEscapedAscii,
-      .PrintValue = &PrintIntValue,
-      .EndField = &PrintComma,
-      .StartField = &PrintJsonHeader,
-      .End = &PrintCloseBrace,
-  };
-}
-
-static Printer getTextPrinter(void) {
-  return (Printer){
-      .Start = &PrintVoid,
-      .ArrayStart = &PrintVoid,
-      .ArraySeparator = &PrintComma,
-      .ArrayEnd = &PrintVoid,
-      .PrintString = &PrintString,
-      .PrintValue = &PrintDecHexValue,
-      .EndField = &PrintLineFeed,
-      .StartField = &PrintAlignedHeader,
-      .End = &PrintVoid,
-  };
-}
-
-// Prints a named numeric value in both decimal and hexadecimal.
-static void PrintN(const Printer p, const char* field, int value) {
-  p.StartField(field);
-  p.PrintValue(value);
-  p.EndField();
-}
-
-// Prints a named string.
-static void PrintS(const Printer p, const char* field, const char* value) {
-  p.StartField(field);
-  p.PrintString(value);
-  p.EndField();
-}
-
-static int cmp(const void* p1, const void* p2) {
-  return strcmp(*(const char* const*)p1, *(const char* const*)p2);
-}
-
-#define DEFINE_PRINT_FLAGS(HasFeature, FeatureName, FeatureType, LastEnum) \
-  static void PrintFlags(const Printer p, const FeatureType* features) {   \
-    size_t i;                                                              \
-    const char* ptrs[LastEnum] = {0};                                      \
-    size_t count = 0;                                                      \
-    for (i = 0; i < LastEnum; ++i) {                                       \
-      if (HasFeature(features, i)) {                                       \
-        ptrs[count] = FeatureName(i);                                      \
-        ++count;                                                           \
-      }                                                                    \
-    }                                                                      \
-    qsort((void*)ptrs, count, sizeof(char*), cmp);                         \
-    p.StartField("flags");                                                 \
-    p.ArrayStart();                                                        \
-    for (i = 0; i < count; ++i) {                                          \
-      if (i > 0) p.ArraySeparator();                                       \
-      p.PrintString(ptrs[i]);                                              \
-    }                                                                      \
-    p.ArrayEnd();                                                          \
+// Walks a TreeValue and print it as json.
+static void printJson(const TreeValue* current) {
+  assert(current);
+  switch (current->type) {
+    case TNT_INT:
+      printf("%d", current->integer);
+      break;
+    case TNT_STRING:
+      printJsonString(current->string);
+      break;
+    case TNT_ARRAY:
+      putchar('[');
+      if (current->next) printJson(current->next);
+      putchar(']');
+      break;
+    case TNT_MAP:
+      putchar('{');
+      if (current->next) printJson(current->next);
+      putchar('}');
+      break;
+    case TNT_MAP_ENTRY:
+      printf("\"%s\":", current->string);
+      printJson(current->value);
+      if (current->next) {
+        putchar(',');
+        printJson(current->next);
+      }
+      break;
+    case TNT_ARRAY_ELEMENT:
+      printJson(current->value);
+      if (current->next) {
+        putchar(',');
+        printJson(current->next);
+      }
+      break;
   }
-
-#if defined(CPU_FEATURES_ARCH_X86)
-DEFINE_PRINT_FLAGS(GetX86FeaturesEnumValue, GetX86FeaturesEnumName, X86Features,
-                   X86_LAST_)
-#elif defined(CPU_FEATURES_ARCH_ARM)
-DEFINE_PRINT_FLAGS(GetArmFeaturesEnumValue, GetArmFeaturesEnumName, ArmFeatures,
-                   ARM_LAST_)
-#elif defined(CPU_FEATURES_ARCH_AARCH64)
-DEFINE_PRINT_FLAGS(GetAarch64FeaturesEnumValue, GetAarch64FeaturesEnumName,
-                   Aarch64Features, AARCH64_LAST_)
-#elif defined(CPU_FEATURES_ARCH_MIPS)
-DEFINE_PRINT_FLAGS(GetMipsFeaturesEnumValue, GetMipsFeaturesEnumName,
-                   MipsFeatures, MIPS_LAST_)
-#elif defined(CPU_FEATURES_ARCH_PPC)
-DEFINE_PRINT_FLAGS(GetPPCFeaturesEnumValue, GetPPCFeaturesEnumName, PPCFeatures,
-                   PPC_LAST_)
-#endif
-
-static void PrintFeatures(const Printer printer) {
-#if defined(CPU_FEATURES_ARCH_X86)
-  char brand_string[49];
-  const X86Info info = GetX86Info();
-  FillX86BrandString(brand_string);
-  PrintS(printer, "arch", "x86");
-  PrintS(printer, "brand", brand_string);
-  PrintN(printer, "family", info.family);
-  PrintN(printer, "model", info.model);
-  PrintN(printer, "stepping", info.stepping);
-  PrintS(printer, "uarch",
-         GetX86MicroarchitectureName(GetX86Microarchitecture(&info)));
-  PrintFlags(printer, &info.features);
-#elif defined(CPU_FEATURES_ARCH_ARM)
-  const ArmInfo info = GetArmInfo();
-  PrintS(printer, "arch", "ARM");
-  PrintN(printer, "implementer", info.implementer);
-  PrintN(printer, "architecture", info.architecture);
-  PrintN(printer, "variant", info.variant);
-  PrintN(printer, "part", info.part);
-  PrintN(printer, "revision", info.revision);
-  PrintFlags(printer, &info.features);
-#elif defined(CPU_FEATURES_ARCH_AARCH64)
-  const Aarch64Info info = GetAarch64Info();
-  PrintS(printer, "arch", "aarch64");
-  PrintN(printer, "implementer", info.implementer);
-  PrintN(printer, "variant", info.variant);
-  PrintN(printer, "part", info.part);
-  PrintN(printer, "revision", info.revision);
-  PrintFlags(printer, &info.features);
-#elif defined(CPU_FEATURES_ARCH_MIPS)
-  (void)&PrintN;  // Remove unused function warning.
-  const MipsInfo info = GetMipsInfo();
-  PrintS(printer, "arch", "mips");
-  PrintFlags(printer, &info.features);
-#elif defined(CPU_FEATURES_ARCH_PPC)
-  (void)&PrintN;  // Remove unused function warning.
-  const PPCInfo info = GetPPCInfo();
-  const PPCPlatformStrings strings = GetPPCPlatformStrings();
-  PrintS(printer, "arch", "ppc");
-  PrintS(printer, "platform", strings.platform);
-  PrintS(printer, "model", strings.model);
-  PrintS(printer, "machine", strings.machine);
-  PrintS(printer, "cpu", strings.cpu);
-  PrintS(printer, "instruction set", strings.type.platform);
-  PrintS(printer, "microarchitecture", strings.type.base_platform);
-  PrintFlags(printer, &info.features);
-#endif
 }
 
+// Walks a TreeValue and print it as text.
+static void printTextField(const TreeValue* current) {
+  switch (current->type) {
+    case TNT_INT:
+      printf("%3d (0x%02X)", current->integer, current->integer);
+      break;
+    case TNT_STRING:
+      fputs(current->string, stdout);
+      break;
+    case TNT_ARRAY:
+      if (current->next) printTextField(current->next);
+      break;
+    case TNT_MAP:
+      if (current->next) printJson(current->next);
+      break;
+    case TNT_MAP_ENTRY:
+      printf("%-15s : ", current->string);
+      printTextField(current->value);
+      if (current->next) {
+        putchar('\n');
+        printTextField(current->next);
+      }
+      break;
+    case TNT_ARRAY_ELEMENT:
+      printTextField(current->value);
+      if (current->next) {
+        putchar(',');
+        printTextField(current->next);
+      }
+      break;
+  }
+}
+
+static void printTextRoot(const TreeValue* current) {
+  if (current->type == TNT_MAP && current->next) printTextField(current->next);
+}
 static void showUsage(const char* name) {
   printf(
       "\n"
@@ -224,13 +309,67 @@
       name);
 }
 
+static TreeValue* CreateTree(BumpAllocator* BA) {
+  TreeValue* root = CreateMap(BA);
+#if defined(CPU_FEATURES_ARCH_X86)
+  char brand_string[49];
+  const X86Info info = GetX86Info();
+  FillX86BrandString(brand_string);
+  AddMapEntry(BA, root, "arch", CreateString(BA, "x86"));
+  AddMapEntry(BA, root, "brand", CreateString(BA, brand_string));
+  AddMapEntry(BA, root, "family", CreateInt(BA, info.family));
+  AddMapEntry(BA, root, "model", CreateInt(BA, info.model));
+  AddMapEntry(BA, root, "stepping", CreateInt(BA, info.stepping));
+  AddMapEntry(BA, root, "uarch",
+              CreateString(BA, GetX86MicroarchitectureName(
+                                   GetX86Microarchitecture(&info))));
+  AddFlags(BA, root, &info.features);
+#elif defined(CPU_FEATURES_ARCH_ARM)
+  const ArmInfo info = GetArmInfo();
+  AddMapEntry(BA, root, "arch", CreateString(BA, "ARM"));
+  AddMapEntry(BA, root, "implementer", CreateInt(BA, info.implementer));
+  AddMapEntry(BA, root, "architecture", CreateInt(BA, info.architecture));
+  AddMapEntry(BA, root, "variant", CreateInt(BA, info.variant));
+  AddMapEntry(BA, root, "part", CreateInt(BA, info.part));
+  AddMapEntry(BA, root, "revision", CreateInt(BA, info.revision));
+  AddFlags(BA, root, &info.features);
+#elif defined(CPU_FEATURES_ARCH_AARCH64)
+  const Aarch64Info info = GetAarch64Info();
+  AddMapEntry(BA, root, "arch", CreateString(BA, "aarch64"));
+  AddMapEntry(BA, root, "implementer", CreateInt(BA, info.implementer));
+  AddMapEntry(BA, root, "variant", CreateInt(BA, info.variant));
+  AddMapEntry(BA, root, "part", CreateInt(BA, info.part));
+  AddMapEntry(BA, root, "revision", CreateInt(BA, info.revision));
+  AddFlags(BA, root, &info.features);
+#elif defined(CPU_FEATURES_ARCH_MIPS)
+  const MipsInfo info = GetMipsInfo();
+  AddMapEntry(BA, root, "arch", CreateString(BA, "mips"));
+  AddFlags(BA, root, &info.features);
+#elif defined(CPU_FEATURES_ARCH_PPC)
+  const PPCInfo info = GetPPCInfo();
+  const PPCPlatformStrings strings = GetPPCPlatformStrings();
+  AddMapEntry(BA, root, "arch", CreateString(BA, "ppc"));
+  AddMapEntry(BA, root, "platform", CreateString(BA, strings.platform));
+  AddMapEntry(BA, root, "model", CreateString(BA, strings.model));
+  AddMapEntry(BA, root, "machine", CreateString(BA, strings.machine));
+  AddMapEntry(BA, root, "cpu", CreateString(BA, strings.cpu));
+  AddMapEntry(BA, root, "instruction", CreateString(BA, strings.type.platform));
+  AddMapEntry(BA, root, "microarchitecture",
+              CreateString(BA, strings.type.base_platform));
+  AddFlags(BA, root, &info.features);
+#endif
+  return root;
+}
+
 int main(int argc, char** argv) {
-  Printer printer = getTextPrinter();
+  BumpAllocator BA = BA_Create(64 * 1024);
+  const TreeValue* const root = CreateTree(&BA);
+  bool outputJson = false;
   int i = 1;
   for (; i < argc; ++i) {
     const char* arg = argv[i];
     if (strcmp(arg, "-j") == 0 || strcmp(arg, "--json") == 0) {
-      printer = getJsonPrinter();
+      outputJson = true;
     } else {
       showUsage(argv[0]);
       if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0)
@@ -238,9 +377,10 @@
       return EXIT_FAILURE;
     }
   }
-  printer.Start();
-  PrintFeatures(printer);
-  printer.End();
-  PrintLineFeed();
+  if (outputJson)
+    printJson(root);
+  else
+    printTextRoot(root);
+  putchar('\n');
   return EXIT_SUCCESS;
 }