Author: David Spickett
Date: 2023-06-21T09:28:48Z
New Revision: 8b73a2e8219127b707280b689edb20753849bd4c

URL: 
https://github.com/llvm/llvm-project/commit/8b73a2e8219127b707280b689edb20753849bd4c
DIFF: 
https://github.com/llvm/llvm-project/commit/8b73a2e8219127b707280b689edb20753849bd4c.diff

LOG: [LLDB] Add table formatting for register fields

This will be used by the "register info" command to show
the layout of register contents. For example if we have
these fields coming in from XML:
```
<field name="D" start="0" end="7"/>
<field name="C" start="8" end="15"/>
<field name="B" start="16" end="23"/>
<field name="A" start="24" end="31"/>
```
We get:
```
| 31-24 | 23-16 | 15-8 | 7-0 |
|-------|-------|------|-----|
|   A   |   B   |  C   |  D  |
```
Note that this is only the layout, not the values.
For values, use "register read".

The tables' columns are center padded (left bias
if there's an odd padding) and will wrap if the terminal width
is too low.

```
| 31-24 | 23-16 |
|-------|-------|
|   A   |   B   |

| 15-8 | 7-0 |
|------|-----|
|  C   |  D  |
```

This means we match the horizontal format seen in many architecture
manuals but don't spam the user with lots of misaligned text when the
output gets very long.

Reviewed By: jasonmolenda

Differential Revision: https://reviews.llvm.org/D152917

Added: 
    

Modified: 
    lldb/include/lldb/Target/RegisterFlags.h
    lldb/source/Target/RegisterFlags.cpp
    lldb/unittests/Target/RegisterFlagsTest.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/RegisterFlags.h 
b/lldb/include/lldb/Target/RegisterFlags.h
index 2dd20f9ad5fbf..0b9961e3d7a3f 100644
--- a/lldb/include/lldb/Target/RegisterFlags.h
+++ b/lldb/include/lldb/Target/RegisterFlags.h
@@ -93,6 +93,13 @@ class RegisterFlags {
   unsigned GetSize() const { return m_size; }
   void log(Log *log) const;
 
+  /// Produce a text table showing the layout of all the fields. Unamed/padding
+  /// fields will be included, with only their positions shown.
+  /// max_width will be the width in characters of the terminal you are
+  /// going to print the table to. If the table would exceed this width, it 
will
+  /// be split into many tables as needed.
+  std::string AsTable(uint32_t max_width) const;
+
 private:
   const std::string m_id;
   /// Size in bytes

diff  --git a/lldb/source/Target/RegisterFlags.cpp 
b/lldb/source/Target/RegisterFlags.cpp
index 8bdcb61b05b23..9b12815fa59a9 100644
--- a/lldb/source/Target/RegisterFlags.cpp
+++ b/lldb/source/Target/RegisterFlags.cpp
@@ -7,7 +7,9 @@
 
//===----------------------------------------------------------------------===//
 
 #include "lldb/Target/RegisterFlags.h"
+#include "lldb/Utility/StreamString.h"
 
+#include <numeric>
 #include <optional>
 
 using namespace lldb_private;
@@ -91,3 +93,85 @@ void RegisterFlags::log(Log *log) const {
   for (const Field &field : m_fields)
     field.log(log);
 }
+
+static StreamString FormatCell(const StreamString &content,
+                               unsigned column_width) {
+  unsigned pad = column_width - content.GetString().size();
+  std::string pad_l;
+  std::string pad_r;
+  if (pad) {
+    pad_l = std::string(pad / 2, ' ');
+    pad_r = std::string((pad / 2) + (pad % 2), ' ');
+  }
+
+  StreamString aligned;
+  aligned.Printf("|%s%s%s", pad_l.c_str(), content.GetString().data(),
+                 pad_r.c_str());
+  return aligned;
+}
+
+static void EmitTable(std::string &out, std::array<std::string, 3> &table) {
+  // Close the table.
+  for (std::string &line : table)
+    line += '|';
+
+  out += std::accumulate(table.begin() + 1, table.end(), table.front(),
+                         [](std::string lhs, const auto &rhs) {
+                           return std::move(lhs) + "\n" + rhs;
+                         });
+}
+
+std::string RegisterFlags::AsTable(uint32_t max_width) const {
+  std::string table;
+  // position / gridline / name
+  std::array<std::string, 3> lines;
+  uint32_t current_width = 0;
+
+  for (const RegisterFlags::Field &field : m_fields) {
+    StreamString position;
+    if (field.GetEnd() == field.GetStart())
+      position.Printf(" %d ", field.GetEnd());
+    else
+      position.Printf(" %d-%d ", field.GetEnd(), field.GetStart());
+
+    StreamString name;
+    name.Printf(" %s ", field.GetName().c_str());
+
+    unsigned column_width = position.GetString().size();
+    unsigned name_width = name.GetString().size();
+    if (name_width > column_width)
+      column_width = name_width;
+
+    // If the next column would overflow and we have already formatted at least
+    // one column, put out what we have and move to a new table on the next 
line
+    // (+1 here because we need to cap the ends with '|'). If this is the first
+    // column, just let it overflow and we'll wrap next time around. There's 
not
+    // mich we can do with a very small terminal.
+    if (current_width && ((current_width + column_width + 1) >= max_width)) {
+      EmitTable(table, lines);
+      // Blank line between each.
+      table += "\n\n";
+
+      for (std::string &line : lines)
+        line.clear();
+      current_width = 0;
+    }
+
+    StreamString aligned_position = FormatCell(position, column_width);
+    lines[0] += aligned_position.GetString();
+    StreamString grid;
+    grid << '|' << std::string(column_width, '-');
+    lines[1] += grid.GetString();
+    StreamString aligned_name = FormatCell(name, column_width);
+    lines[2] += aligned_name.GetString();
+
+    // +1 for the left side '|'.
+    current_width += column_width + 1;
+  }
+
+  // If we didn't overflow and still have table to print out.
+  if (lines[0].size())
+    EmitTable(table, lines);
+
+  return table;
+}

diff  --git a/lldb/unittests/Target/RegisterFlagsTest.cpp 
b/lldb/unittests/Target/RegisterFlagsTest.cpp
index c24a6d9bf6c32..194e05959c165 100644
--- a/lldb/unittests/Target/RegisterFlagsTest.cpp
+++ b/lldb/unittests/Target/RegisterFlagsTest.cpp
@@ -137,3 +137,121 @@ TEST(RegisterFieldsTest, ReverseFieldOrder) {
                      make_field(28, 28)});
   ASSERT_EQ(0x00000005ULL, rf3.ReverseFieldOrder(0xA0000000));
 }
+
+TEST(RegisterFlagsTest, AsTable) {
+  // Anonymous fields are shown with an empty name cell,
+  // whether they are known up front or added during construction.
+  RegisterFlags anon_field("", 4, {make_field(0, 31)});
+  ASSERT_EQ("| 31-0 |\n"
+            "|------|\n"
+            "|      |",
+            anon_field.AsTable(100));
+
+  RegisterFlags anon_with_pad("", 4, {make_field(16, 31)});
+  ASSERT_EQ("| 31-16 | 15-0 |\n"
+            "|-------|------|\n"
+            "|       |      |",
+            anon_with_pad.AsTable(100));
+
+  // Use the wider of position and name to set the column width.
+  RegisterFlags name_wider("", 4, {RegisterFlags::Field("aardvark", 0, 31)});
+  ASSERT_EQ("|   31-0   |\n"
+            "|----------|\n"
+            "| aardvark |",
+            name_wider.AsTable(100));
+  // When the padding is an odd number, put the remaining 1 on the right.
+  RegisterFlags pos_wider("", 4, {RegisterFlags::Field("?", 0, 31)});
+  ASSERT_EQ("| 31-0 |\n"
+            "|------|\n"
+            "|  ?   |",
+            pos_wider.AsTable(100));
+
+  // Single bit fields don't need to show start and end, just one of them.
+  RegisterFlags single_bit("", 4, {make_field(31, 31)});
+  ASSERT_EQ("| 31 | 30-0 |\n"
+            "|----|------|\n"
+            "|    |      |",
+            single_bit.AsTable(100));
+
+  // Columns are printed horizontally if max width allows.
+  RegisterFlags many_fields("", 4,
+                            {RegisterFlags::Field("cat", 28, 31),
+                             RegisterFlags::Field("pigeon", 20, 23),
+                             RegisterFlags::Field("wolf", 12, 12),
+                             RegisterFlags::Field("x", 0, 4)});
+  ASSERT_EQ("| 31-28 | 27-24 | 23-20  | 19-13 |  12  | 11-5 | 4-0 |\n"
+            "|-------|-------|--------|-------|------|------|-----|\n"
+            "|  cat  |       | pigeon |       | wolf |      |  x  |",
+            many_fields.AsTable(100));
+
+  // max_width tells us when we need to split into further tables.
+  // Here no split is needed.
+  RegisterFlags exact_max_single_col("", 4, {RegisterFlags::Field("?", 0, 
31)});
+  ASSERT_EQ("| 31-0 |\n"
+            "|------|\n"
+            "|  ?   |",
+            exact_max_single_col.AsTable(9));
+  RegisterFlags exact_max_two_col(
+      "", 4,
+      {RegisterFlags::Field("?", 16, 31), RegisterFlags::Field("#", 0, 15)});
+  ASSERT_EQ("| 31-16 | 15-0 |\n"
+            "|-------|------|\n"
+            "|   ?   |  #   |",
+            exact_max_two_col.AsTable(16));
+
+  // If max is less than a single column, just print the single column. The 
user
+  // will have to put up with some wrapping in this niche case.
+  RegisterFlags zero_max_single_col("", 4, {RegisterFlags::Field("?", 0, 31)});
+  ASSERT_EQ("| 31-0 |\n"
+            "|------|\n"
+            "|  ?   |",
+            zero_max_single_col.AsTable(0));
+  // Same logic for any following columns. Effectively making a "vertical"
+  // table, just with more grid lines.
+  RegisterFlags zero_max_two_col(
+      "", 4,
+      {RegisterFlags::Field("?", 16, 31), RegisterFlags::Field("#", 0, 15)});
+  ASSERT_EQ("| 31-16 |\n"
+            "|-------|\n"
+            "|   ?   |\n"
+            "\n"
+            "| 15-0 |\n"
+            "|------|\n"
+            "|  #   |",
+            zero_max_two_col.AsTable(0));
+
+  RegisterFlags max_less_than_single_col("", 4,
+                                         {RegisterFlags::Field("?", 0, 31)});
+  ASSERT_EQ("| 31-0 |\n"
+            "|------|\n"
+            "|  ?   |",
+            max_less_than_single_col.AsTable(3));
+  RegisterFlags max_less_than_two_col(
+      "", 4,
+      {RegisterFlags::Field("?", 16, 31), RegisterFlags::Field("#", 0, 15)});
+  ASSERT_EQ("| 31-16 |\n"
+            "|-------|\n"
+            "|   ?   |\n"
+            "\n"
+            "| 15-0 |\n"
+            "|------|\n"
+            "|  #   |",
+            max_less_than_two_col.AsTable(9));
+  RegisterFlags max_many_columns(
+      "", 4,
+      {RegisterFlags::Field("A", 24, 31), RegisterFlags::Field("B", 16, 23),
+       RegisterFlags::Field("C", 8, 15),
+       RegisterFlags::Field("really long name", 0, 7)});
+  ASSERT_EQ("| 31-24 | 23-16 |\n"
+            "|-------|-------|\n"
+            "|   A   |   B   |\n"
+            "\n"
+            "| 15-8 |\n"
+            "|------|\n"
+            "|  C   |\n"
+            "\n"
+            "|       7-0        |\n"
+            "|------------------|\n"
+            "| really long name |",
+            max_many_columns.AsTable(23));
+}


        
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to