DavidSpickett created this revision. Herald added a project: All. DavidSpickett requested review of this revision. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
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. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D152917 Files: lldb/include/lldb/Target/RegisterFlags.h lldb/source/Target/RegisterFlags.cpp lldb/unittests/Target/RegisterFlagsTest.cpp
Index: lldb/unittests/Target/RegisterFlagsTest.cpp =================================================================== --- lldb/unittests/Target/RegisterFlagsTest.cpp +++ 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)); +} Index: lldb/source/Target/RegisterFlags.cpp =================================================================== --- lldb/source/Target/RegisterFlags.cpp +++ 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 @@ 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; +} Index: lldb/include/lldb/Target/RegisterFlags.h =================================================================== --- lldb/include/lldb/Target/RegisterFlags.h +++ 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
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits