diff --git a/lldb/include/lldb/Target/RegisterFlags.h b/lldb/include/lldb/Target/RegisterFlags.h --- a/lldb/include/lldb/Target/RegisterFlags.h +++ b/lldb/include/lldb/Target/RegisterFlags.h @@ -93,6 +93,13 @@ 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 --- 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 #include using namespace lldb_private; @@ -91,3 +93,85 @@ 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 &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 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 --- a/lldb/unittests/Target/RegisterFlagsTest.cpp +++ b/lldb/unittests/Target/RegisterFlagsTest.cpp @@ -137,3 +137,121 @@ 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)); +}