diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/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. diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/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 && diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/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; diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -10,7 +10,9 @@ #include "NamespaceEndCommentsFixer.h" #include "WhitespaceManager.h" #include "llvm/Support/Debug.h" +#include #include +#include #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 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>; + + // 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{}); + 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); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/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)");