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
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits