feg208 created this revision.
feg208 added reviewers: tinloaf, djasper, klimek, curdeius.
feg208 requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

This adds a new formatter to arrange array of struct initializers into neat 
columns


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D101868

Files:
  clang/docs/ClangFormatStyleOptions.rst
  clang/include/clang/Format/Format.h
  clang/lib/Format/Format.cpp
  clang/lib/Format/UnwrappedLineFormatter.cpp
  clang/unittests/Format/FormatTest.cpp

Index: clang/unittests/Format/FormatTest.cpp
===================================================================
--- clang/unittests/Format/FormatTest.cpp
+++ clang/unittests/Format/FormatTest.cpp
@@ -16347,6 +16347,29 @@
                getLLVMStyle());
 }
 
+TEST_F(FormatTest, CatchAlignArrayOfStructuresInit) {
+  auto Style = getLLVMStyle();
+  Style.AlignArrayOfStructuresInit = true;
+  verifyFormat("struct test demo[] = {\n"
+               "    {56, 23,    \"hello\" },\n"
+               "    {-1, 93463, \"world\" },\n"
+               "    {7,  5,     \"!!\"    }\n"
+               "};\n",
+               Style);
+  verifyFormat("struct test demo[3] = {\n"
+               "    {56, 23,    \"hello\" },\n"
+               "    {-1, 93463, \"world\" },\n"
+               "    {7,  5,     \"!!\"    }\n"
+               "};\n",
+               Style);
+  verifyFormat("struct test demo[3] = {\n"
+               "    {int{56}, 23,    \"hello\" },\n"
+               "    {int{-1}, 93463, \"world\" },\n"
+               "    {int{7},  5,     \"!!\"    }\n"
+               "};\n",
+               Style);
+}
+
 TEST_F(FormatTest, UnderstandsPragmas) {
   verifyFormat("#pragma omp reduction(| : var)");
   verifyFormat("#pragma omp reduction(+ : var)");
Index: clang/lib/Format/UnwrappedLineFormatter.cpp
===================================================================
--- clang/lib/Format/UnwrappedLineFormatter.cpp
+++ clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -10,7 +10,9 @@
 #include "NamespaceEndCommentsFixer.h"
 #include "WhitespaceManager.h"
 #include "llvm/Support/Debug.h"
+#include <algorithm>
 #include <queue>
+#include <vector>
 
 #define DEBUG_TYPE "format-formatter"
 
@@ -26,6 +28,62 @@
          NextNext && NextNext->is(tok::l_brace);
 }
 
+// The notion here is that we walk through the annotated line looking for
+// things like static initialization of arrays and flag them
+bool isArrayOfStructuresInit(const AnnotatedLine &Line) {
+  if (!Line.MustBeDeclaration)
+    return false;
+  const auto *CurrentToken = Line.First;
+  enum class DetectAsiFsm {
+    null,
+    in_struct_decl,
+    in_type_decl,
+    in_var_name_decl,
+    in_bracket_decl,
+    finished_bracket_decl,
+    outer_l_brace
+  };
+  DetectAsiFsm AsiFsm{DetectAsiFsm::null};
+  while (CurrentToken != Line.Last && CurrentToken != nullptr) {
+    if (CurrentToken->is(tok::kw_struct)) {
+      if (AsiFsm != DetectAsiFsm::null)
+        return false;
+      AsiFsm = DetectAsiFsm::in_struct_decl;
+    } else if (CurrentToken->is(tok::identifier)) {
+      switch (AsiFsm) {
+      case DetectAsiFsm::null:
+        [[clang::fallthrough]];
+      case DetectAsiFsm::in_struct_decl:
+        AsiFsm = DetectAsiFsm::in_type_decl;
+        break;
+      case DetectAsiFsm::in_type_decl:
+        AsiFsm = DetectAsiFsm::in_var_name_decl;
+        break;
+      default:
+        return false;
+      }
+    } else if (CurrentToken->is(tok::l_square)) {
+      if (AsiFsm != DetectAsiFsm::in_var_name_decl)
+        return false;
+      AsiFsm = DetectAsiFsm::in_bracket_decl;
+    } else if (CurrentToken->is(tok::numeric_constant)) {
+      if (AsiFsm != DetectAsiFsm::in_bracket_decl)
+        return false;
+    } else if (CurrentToken->is(tok::r_square)) {
+      if (AsiFsm != DetectAsiFsm::in_bracket_decl)
+        return false;
+      AsiFsm = DetectAsiFsm::finished_bracket_decl;
+    } else if (CurrentToken->is(tok::l_brace)) {
+      if (AsiFsm == DetectAsiFsm::finished_bracket_decl)
+        AsiFsm = DetectAsiFsm::outer_l_brace;
+      else
+        return AsiFsm == DetectAsiFsm::outer_l_brace;
+    }
+    CurrentToken = CurrentToken->getNextNonComment();
+  }
+  return false;
+}
+
 /// Tracks the indent level of \c AnnotatedLines across levels.
 ///
 /// \c nextLine must be called for each \c AnnotatedLine, after which \c
@@ -1101,6 +1159,176 @@
   llvm::SpecificBumpPtrAllocator<StateNode> Allocator;
 };
 
+/// Breaks a static array of struct initializers into regular
+/// columns
+class AlignedArrayOfStructuresInitLineFormatter : public LineFormatter {
+  struct FormatArgs {
+    LineState &State;
+    unsigned &Penalty;
+    unsigned FirstIndent;
+    unsigned FirstStartColumn;
+    bool DryRun;
+  };
+
+public:
+  AlignedArrayOfStructuresInitLineFormatter(
+      ContinuationIndenter *Indenter, WhitespaceManager *Whitespaces,
+      const FormatStyle &Style, UnwrappedLineFormatter *BlockFormatter)
+      : LineFormatter(Indenter, Whitespaces, Style, BlockFormatter) {}
+
+  /// Formats the line by finding line breaks with line lengths
+  /// below the column limit that still orders the array initializers
+  /// into tidy columns
+  unsigned formatLine(const AnnotatedLine &Line, unsigned FirstIndent,
+                      unsigned FirstStartColumn, bool DryRun) override {
+    unsigned Penalty = 0;
+    auto LayoutMatrix = getLayoutMatrix(Line);
+    LineState State =
+        Indenter->getInitialState(FirstIndent, FirstStartColumn, &Line, DryRun);
+    unsigned Depth = 0;
+    while (State.NextToken) {
+      auto *CurrentToken = State.NextToken->Previous;
+      if (CurrentToken->is(tok::l_brace)) {
+        FormatArgs Args{State, Penalty, FirstIndent, FirstStartColumn, DryRun};
+        enterInitializer(LayoutMatrix, Args, Depth + 1);
+      } else {
+        skipToken(State, Penalty, DryRun);
+      }
+    }
+    return Penalty;
+  }
+
+private:
+  using Matrix = std::vector<std::vector<unsigned>>;
+
+  // Whatever the newline situation is with this line keep it and move on
+  void skipToken(LineState &State, unsigned &Penalty, bool DryRun,
+                 unsigned ExtraSpaces = 0) {
+    bool Newline =
+        Indenter->mustBreak(State) ||
+        (Indenter->canBreak(State) && State.NextToken->NewlinesBefore > 0);
+    formatChildren(State, Newline, DryRun, Penalty);
+    Indenter->addTokenToState(State, Newline, DryRun, ExtraSpaces);
+  }
+
+  // Break the line
+  void insertBreak(LineState &State, unsigned &Penalty, bool DryRun) {
+    formatChildren(State, true, DryRun, Penalty);
+    Indenter->addTokenToState(State, true, DryRun);
+  }
+
+  void enterInitializer(const Matrix &Layout, FormatArgs Args, unsigned Depth) {
+    if (Depth == 1) {
+      insertBreak(Args.State, Args.Penalty, Args.DryRun);
+      return enterInitializer(Layout, Args, Depth + 1);
+    }
+    unsigned Row = 0U;
+    unsigned Column = 0U;
+    while (Args.State.NextToken) {
+      auto *CurrentToken = Args.State.NextToken->Previous;
+      if (CurrentToken->is(tok::l_brace)) {
+        ++Depth;
+      } else if (CurrentToken->is(tok::r_brace)) {
+        Depth--;
+      }
+
+      if (CurrentToken->is(tok::r_brace)) {
+        if (Depth == 2) {
+          if (Args.State.NextToken->is(tok::r_brace)) {
+            insertBreak(Args.State, Args.Penalty, Args.DryRun);
+          } else {
+            skipToken(Args.State, Args.Penalty, Args.DryRun);
+            insertBreak(Args.State, Args.Penalty, Args.DryRun);
+          }
+          Column = 0;
+          Row++;
+        } else {
+          skipToken(Args.State, Args.Penalty, Args.DryRun);
+        }
+      } else if (CurrentToken->is(tok::comma) && Depth == 3) {
+        skipToken(Args.State, Args.Penalty, Args.DryRun, Layout[Row][Column++]);
+      } else {
+        if (Args.State.NextToken->is(tok::r_brace) && Depth == 3) {
+          skipToken(Args.State, Args.Penalty, Args.DryRun,
+                    Layout[Row][Column++] + 1U);
+        } else {
+          skipToken(Args.State, Args.Penalty, Args.DryRun);
+        }
+      }
+    }
+  }
+
+  static const FormatToken *enterInitializer(const FormatToken *CurrentToken,
+                                             Matrix &Layout,
+                                             const AnnotatedLine &Line,
+                                             unsigned Depth) {
+    if (Depth < 2) {
+      while (CurrentToken != Line.Last) {
+        if (CurrentToken->is(tok::l_brace)) {
+          CurrentToken = enterInitializer(CurrentToken->getNextNonComment(),
+                                          Layout, Line, Depth + 1);
+        } else {
+          CurrentToken = CurrentToken->getNextNonComment();
+        }
+      }
+      return CurrentToken;
+    }
+    Layout.emplace_back(std::vector<unsigned>{});
+    auto Row = Layout.size() - 1;
+    Layout[Row].push_back(0);
+    auto Column = 0U;
+    while (CurrentToken != Line.Last) {
+      if (CurrentToken->is(tok::l_brace)) {
+        Depth++;
+      } else if (CurrentToken->is(tok::r_brace)) {
+        if (Depth == 2)
+          return CurrentToken->getNextNonComment();
+        Depth--;
+      }
+      if (CurrentToken->is(tok::comma)) {
+        Column++;
+        Layout[Row].push_back(0);
+      } else {
+        Layout[Row][Column] += CurrentToken->ColumnWidth;
+      }
+      CurrentToken = CurrentToken->getNextNonComment();
+    }
+    return CurrentToken;
+  }
+
+  static Matrix getLayoutMatrix(const AnnotatedLine &Line) {
+    Matrix LayoutMatrix{};
+    const auto *CurrentToken = Line.First;
+    unsigned Depth = 0;
+    // First step get the Column widths
+    while (CurrentToken != Line.Last) {
+      if (CurrentToken->is(tok::l_brace)) {
+        CurrentToken = enterInitializer(CurrentToken->getNextNonComment(),
+                                        LayoutMatrix, Line, ++Depth);
+      } else {
+        CurrentToken = CurrentToken->getNextNonComment();
+      }
+    }
+    // Now adjust the values at each column to just contain the number
+    // of extra spaces to add
+    auto Column = 0U;
+    auto RowCount = LayoutMatrix.size();
+    while (true) {
+      auto MaxColumn = 0U;
+      for (auto Row = 0U; Row < RowCount; Row++) {
+        MaxColumn = std::max(MaxColumn, LayoutMatrix[Row][Column]);
+      }
+      for (auto Row = 0U; Row < RowCount; Row++) {
+        LayoutMatrix[Row][Column] = MaxColumn - LayoutMatrix[Row][Column];
+      }
+      Column++;
+      if (Column >= LayoutMatrix[0].size())
+        break;
+    }
+    return LayoutMatrix;
+  }
+};
+
 } // anonymous namespace
 
 unsigned UnwrappedLineFormatter::format(
@@ -1171,7 +1399,14 @@
             !Style.JavaScriptWrapImports)) ||
           (Style.isCSharp() &&
            TheLine.InPPDirective); // don't split #regions in C#
-      if (Style.ColumnLimit == 0)
+      bool AlignArrayOfStructuresInit = (Style.AlignArrayOfStructuresInit &&
+                                         isArrayOfStructuresInit(TheLine));
+      if (AlignArrayOfStructuresInit) {
+        Penalty += AlignedArrayOfStructuresInitLineFormatter(
+                       Indenter, Whitespaces, Style, this)
+                       .formatLine(TheLine, NextStartColumn + Indent,
+                                   FirstLine ? FirstStartColumn : 0, DryRun);
+      } else if (Style.ColumnLimit == 0)
         NoColumnLimitLineFormatter(Indenter, Whitespaces, Style, this)
             .formatLine(TheLine, NextStartColumn + Indent,
                         FirstLine ? FirstStartColumn : 0, DryRun);
Index: clang/lib/Format/Format.cpp
===================================================================
--- clang/lib/Format/Format.cpp
+++ clang/lib/Format/Format.cpp
@@ -506,6 +506,8 @@
 
     IO.mapOptional("AccessModifierOffset", Style.AccessModifierOffset);
     IO.mapOptional("AlignAfterOpenBracket", Style.AlignAfterOpenBracket);
+    IO.mapOptional("AlignArrayOfStructuresInit",
+                   Style.AlignArrayOfStructuresInit);
     IO.mapOptional("AlignConsecutiveMacros", Style.AlignConsecutiveMacros);
     IO.mapOptional("AlignConsecutiveAssignments",
                    Style.AlignConsecutiveAssignments);
@@ -941,6 +943,7 @@
   LLVMStyle.AccessModifierOffset = -2;
   LLVMStyle.AlignEscapedNewlines = FormatStyle::ENAS_Right;
   LLVMStyle.AlignAfterOpenBracket = FormatStyle::BAS_Align;
+  LLVMStyle.AlignArrayOfStructuresInit = false;
   LLVMStyle.AlignOperands = FormatStyle::OAS_Align;
   LLVMStyle.AlignTrailingComments = true;
   LLVMStyle.AlignConsecutiveAssignments = FormatStyle::ACS_None;
Index: clang/include/clang/Format/Format.h
===================================================================
--- clang/include/clang/Format/Format.h
+++ clang/include/clang/Format/Format.h
@@ -90,6 +90,18 @@
   /// brackets.
   BracketAlignmentStyle AlignAfterOpenBracket;
 
+  /// if ``true``, when using static initialization for an array of structs
+  /// aligns the fields into columns
+  /// \code
+  ///   struct test demo[] =
+  ///   {
+  ///       {56, 23,        "hello" },
+  ///       {-1, 93463,     "world" },
+  ///       {7,  5,         "!!"    }
+  ///   }
+  /// \endcode
+  bool AlignArrayOfStructuresInit;
+
   /// Styles for alignment of consecutive tokens. Tokens can be assignment signs
   /// (see
   /// ``AlignConsecutiveAssignments``), bitfield member separators (see
@@ -3249,6 +3261,7 @@
   bool operator==(const FormatStyle &R) const {
     return AccessModifierOffset == R.AccessModifierOffset &&
            AlignAfterOpenBracket == R.AlignAfterOpenBracket &&
+           AlignArrayOfStructuresInit == R.AlignArrayOfStructuresInit &&
            AlignConsecutiveAssignments == R.AlignConsecutiveAssignments &&
            AlignConsecutiveBitFields == R.AlignConsecutiveBitFields &&
            AlignConsecutiveDeclarations == R.AlignConsecutiveDeclarations &&
Index: clang/docs/ClangFormatStyleOptions.rst
===================================================================
--- clang/docs/ClangFormatStyleOptions.rst
+++ clang/docs/ClangFormatStyleOptions.rst
@@ -203,6 +203,17 @@
           argument1, argument2);
 
 
+**AlignArrayOfStructuresInit** (``bool``)
+  If ``true``, when using static initialization for an array
+  of structs aligns the fields into columns
+
+  .. code-block:: c
+  struct test demo[] =
+  {
+        {56, 23,        "hello" },
+        {-1, 93463,     "world" },
+        {7,  5,         "!!"    }
+  }
 
 **AlignConsecutiveAssignments** (``AlignConsecutiveStyle``)
   Style of aligning consecutive assignments.
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to