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,35 @@ argument1, argument2); +**AlignArrayOfStructures** (``ArrayInitializerAlignment Style``) + If not ``None``, when using initialization for an array + of structs aligns the fields into justified columns/ + + Possible values: + + * ```Right``` + Right justified columns, e.g.: + + .. code-block:: c + struct test demo[] = + { + {56, 23, "hello"}, + {-1, 93463, "world"}, + { 7, 5, "!!"} + }; + + * ```Left``` + Left justified columns, e.g.: + .. code-block:: c + struct test demo[] = + { + {56, 23, "hello"}, + {-1, 93463, "world"}, + { 7, 5, "!!"} + }; + + * ```None``` + Don't do any special formatting. **AlignConsecutiveAssignments** (``AlignConsecutiveStyle``) Style of aligning consecutive assignments. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -265,6 +265,9 @@ - Makes ``PointerAligment: Right`` working with ``AlignConsecutiveDeclarations``. (Fixes https://llvm.org/PR27353) +- Option ``AlignArrayOfStructure`` has been added to allow for ordering array-like + initializers. + libclang -------- 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,42 @@ /// brackets. BracketAlignmentStyle AlignAfterOpenBracket; + enum ArrayInitializerAlignmentStyle { + /// Align array column and left justify the columns e.g.: + /// \code + /// struct test demo[] = + /// { + /// {56, 23, "hello"}, + /// {-1, 93463, "world"}, + /// {7, 5, "!!" } + /// } + /// \endcode + AIAS_Left, + /// Align array column and right justify the columns e.g.: + /// \code + /// struct test demo[] = + /// { + /// {56, 23, "hello"}, + /// {-1, 93463, "world"}, + /// { 7, 5, "!!"} + /// } + /// \endcode + AIAS_Right, + /// Don't align array initializer columns + AIAS_None + }; + /// if not ``None``, when using initialization for an array of structs + /// aligns the fields into columns + /// \code + /// struct test demo[] = + /// { + /// {56, 23, "hello"}, + /// {-1, 93463, "world"}, + /// { 7, 5, "!!"} + /// } + /// \endcode + ArrayInitializerAlignmentStyle AlignArrayOfStructures; + /// Styles for alignment of consecutive tokens. Tokens can be assignment signs /// (see /// ``AlignConsecutiveAssignments``), bitfield member separators (see @@ -3272,6 +3308,7 @@ bool operator==(const FormatStyle &R) const { return AccessModifierOffset == R.AccessModifierOffset && AlignAfterOpenBracket == R.AlignAfterOpenBracket && + AlignArrayOfStructures == R.AlignArrayOfStructures && 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 @@ -143,6 +143,16 @@ } }; +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, + FormatStyle::ArrayInitializerAlignmentStyle &Value) { + IO.enumCase(Value, "None", FormatStyle::AIAS_None); + IO.enumCase(Value, "Left", FormatStyle::AIAS_Left); + IO.enumCase(Value, "Right", FormatStyle::AIAS_Right); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::ShortIfStyle &Value) { IO.enumCase(Value, "Never", FormatStyle::SIS_Never); @@ -507,6 +517,7 @@ IO.mapOptional("AccessModifierOffset", Style.AccessModifierOffset); IO.mapOptional("AlignAfterOpenBracket", Style.AlignAfterOpenBracket); + IO.mapOptional("AlignArrayOfStructures", Style.AlignArrayOfStructures); IO.mapOptional("AlignConsecutiveMacros", Style.AlignConsecutiveMacros); IO.mapOptional("AlignConsecutiveAssignments", Style.AlignConsecutiveAssignments); @@ -943,6 +954,7 @@ LLVMStyle.AccessModifierOffset = -2; LLVMStyle.AlignEscapedNewlines = FormatStyle::ENAS_Right; LLVMStyle.AlignAfterOpenBracket = FormatStyle::BAS_Align; + LLVMStyle.AlignArrayOfStructures = FormatStyle::AIAS_None; LLVMStyle.AlignOperands = FormatStyle::OAS_Align; LLVMStyle.AlignTrailingComments = true; LLVMStyle.AlignConsecutiveAssignments = FormatStyle::ACS_None; diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -431,6 +431,15 @@ /// The next token in the unwrapped line. FormatToken *Next = nullptr; + /// The first token in set of column elements + bool StartsColumn = false; + + /// This notes the start of the line of an array initializer + bool ArrayInitializerLineStart = false; + + /// This starts an array initializer + bool IsArrayInitializer = false; + /// If this token starts a block, this contains all the unwrapped lines /// in it. SmallVector Children; diff --git a/clang/lib/Format/TokenAnnotator.h b/clang/lib/Format/TokenAnnotator.h --- a/clang/lib/Format/TokenAnnotator.h +++ b/clang/lib/Format/TokenAnnotator.h @@ -31,7 +31,8 @@ LT_ObjCProperty, // An @property line. LT_Other, LT_PreprocessorDirective, - LT_VirtualFunctionDecl + LT_VirtualFunctionDecl, + LT_ArrayOfStructInitializer, }; class AnnotatedLine { @@ -189,6 +190,12 @@ void calculateUnbreakableTailLengths(AnnotatedLine &Line); + void calculateArrayInitializerColumnList(AnnotatedLine &Line); + + FormatToken *calculateInitializerColumnList(AnnotatedLine &Line, + FormatToken *CurrentToken, + unsigned Depth); + const FormatStyle &Style; const AdditionalKeywords &Keywords; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -729,11 +729,25 @@ return false; } + bool couldBeInStructArrayInitializer() const { + if (Contexts.size() < 2) + return false; + // We want to back up no more then 2 context levels i.e. + // . { { <- + const auto End = std::next(Contexts.rbegin(), 2); + auto Last = Contexts.rbegin(); + unsigned Depth = 0; + for (; Last != End; ++Last) { + if (Last->ContextKind == tok::l_brace) + ++Depth; + } + return Depth == 2 && Last->ContextKind != tok::l_brace; + } + bool parseBrace() { if (CurrentToken) { FormatToken *Left = CurrentToken->Previous; Left->ParentBracket = Contexts.back().ContextKind; - if (Contexts.back().CaretFound) Left->setType(TT_ObjCBlockLBrace); Contexts.back().CaretFound = false; @@ -746,10 +760,17 @@ Left->Previous->is(TT_JsTypeColon)) Contexts.back().IsExpression = false; + unsigned CommaCount = 0; while (CurrentToken) { if (CurrentToken->is(tok::r_brace)) { Left->MatchingParen = CurrentToken; CurrentToken->MatchingParen = Left; + if (Style.AlignArrayOfStructures != FormatStyle::AIAS_None) { + if (Left->ParentBracket == tok::l_brace && + couldBeInStructArrayInitializer() && CommaCount > 0) { + Contexts.back().InStructArrayInitializer = true; + } + } next(); return true; } @@ -773,9 +794,11 @@ Style.Language == FormatStyle::LK_JavaScript) Left->setType(TT_DictLiteral); } - if (CurrentToken->is(tok::comma) && - Style.Language == FormatStyle::LK_JavaScript) - Left->setType(TT_DictLiteral); + if (CurrentToken->is(tok::comma)) { + if (Style.Language == FormatStyle::LK_JavaScript) + Left->setType(TT_DictLiteral); + ++CommaCount; + } if (!consumeToken()) return false; } @@ -1339,6 +1362,12 @@ return LT_ObjCMethodDecl; } + for (const auto &ctx : Contexts) { + if (ctx.InStructArrayInitializer) { + return LT_ArrayOfStructInitializer; + } + } + return LT_Other; } @@ -1414,6 +1443,7 @@ bool IsForEachMacro = false; bool InCpp11AttributeSpecifier = false; bool InCSharpAttributeSpecifier = false; + bool InStructArrayInitializer = false; }; /// Puts a new \c Context onto the stack \c Contexts for the lifetime @@ -1429,7 +1459,16 @@ P.Contexts.back().IsExpression)); } - ~ScopedContextCreator() { P.Contexts.pop_back(); } + ~ScopedContextCreator() { + if (P.Style.AlignArrayOfStructures != FormatStyle::AIAS_None) { + if (P.Contexts.back().InStructArrayInitializer) { + P.Contexts.pop_back(); + P.Contexts.back().InStructArrayInitializer = true; + return; + } + } + P.Contexts.pop_back(); + } }; void modifyContext(const FormatToken &Current) { @@ -2473,6 +2512,12 @@ : Line.FirstStartColumn + Line.First->ColumnWidth; FormatToken *Current = Line.First->Next; bool InFunctionDecl = Line.MightBeFunctionDecl; + bool AlignArrayOfStructures = + (Style.AlignArrayOfStructures != FormatStyle::AIAS_None && + Line.Type == LT_ArrayOfStructInitializer); + if (AlignArrayOfStructures) + calculateArrayInitializerColumnList(Line); + while (Current) { if (isFunctionDeclarationName(*Current, Line)) Current->setType(TT_FunctionDeclarationName); @@ -2592,6 +2637,45 @@ } } +void TokenAnnotator::calculateArrayInitializerColumnList(AnnotatedLine &Line) { + if (Line.First == Line.Last) { + return; + } + auto *CurrentToken = Line.First; + CurrentToken->ArrayInitializerLineStart = true; + unsigned Depth = 0; + while (CurrentToken != nullptr && CurrentToken != Line.Last) { + if (CurrentToken->is(tok::l_brace)) { + CurrentToken->IsArrayInitializer = true; + if (CurrentToken->Next != nullptr) + CurrentToken->Next->MustBreakBefore = true; + CurrentToken = + calculateInitializerColumnList(Line, CurrentToken->Next, Depth + 1); + } else { + CurrentToken = CurrentToken->Next; + } + } +} + +FormatToken *TokenAnnotator::calculateInitializerColumnList( + AnnotatedLine &Line, FormatToken *CurrentToken, unsigned Depth) { + while (CurrentToken != nullptr && CurrentToken != Line.Last) { + if (CurrentToken->is(tok::l_brace)) + ++Depth; + else if (CurrentToken->is(tok::r_brace)) + --Depth; + if (Depth == 2 && CurrentToken->isOneOf(tok::l_brace, tok::comma)) { + CurrentToken = CurrentToken->Next; + if (CurrentToken == nullptr) + break; + CurrentToken->StartsColumn = true; + CurrentToken = CurrentToken->Previous; + } + CurrentToken = CurrentToken->Next; + } + return CurrentToken; +} + unsigned TokenAnnotator::splitPenalty(const AnnotatedLine &Line, const FormatToken &Tok, bool InFunctionDecl) { diff --git a/clang/lib/Format/WhitespaceManager.h b/clang/lib/Format/WhitespaceManager.h --- a/clang/lib/Format/WhitespaceManager.h +++ b/clang/lib/Format/WhitespaceManager.h @@ -18,6 +18,8 @@ #include "TokenAnnotator.h" #include "clang/Basic/SourceManager.h" #include "clang/Format/Format.h" +#include "llvm/ADT/SmallVector.h" +#include #include #include @@ -173,6 +175,27 @@ }; private: + struct CellDescription { + unsigned Index = 0; + unsigned Cell = 0; + unsigned EndIndex = 0; + bool HasSplit = false; + CellDescription *NextColumnElement = nullptr; + + constexpr bool operator==(const CellDescription &oth) const { + return Index == oth.Index && Cell == oth.Cell && EndIndex == oth.EndIndex; + } + constexpr bool operator!=(const CellDescription &oth) const { + return !(*this == oth); + } + }; + + struct CellDescriptions { + SmallVector Cells; + unsigned CellCount = 0; + unsigned InitialSpaces = 0; + }; + /// Calculate \c IsTrailingComment, \c TokenLength for the last tokens /// or token parts in a line and \c PreviousEndOfTokenColumn and /// \c EscapedNewlineColumn for the first tokens or token parts in a line. @@ -207,6 +230,89 @@ /// the specified \p Column. void alignEscapedNewlines(unsigned Start, unsigned End, unsigned Column); + /// Align Array Initializers over all \c Changes + void alignArrayInitializers(); + + /// Align Array Initializers from change \p Start to change \p End at + /// the specified \p Column + void alignArrayInitializers(unsigned Start, unsigned End); + + /// Align Array Initializers being careful to right justify the columns + /// as described by \p CellDescs + void alignArrayInitializersRightJustified(CellDescriptions &&CellDescs); + + /// Align Array Initializers being careful to leftt justify the columns + /// as described by \p CellDescs + void alignArrayInitializersLeftJustified(CellDescriptions &&CellDescs); + + /// Calculate the cell width between two indexes + unsigned calculateCellWidth(unsigned Start, unsigned End, + bool WithSpaces = false) const; + + /// Get a set of fully specified CellDescriptions between \p Start and + /// \p End of the change list + CellDescriptions getCells(unsigned Start, unsigned End); + + /// Does this \p Cell contain a split element? + static bool isSplitCell(const CellDescription &Cell); + + /// Get the width of the preceeding cells from \p Start to \p End + template + auto getNetWidth(const I &Start, const I &End, unsigned InitialSpaces) const { + auto NetWidth = InitialSpaces; + for (auto PrevIter = Start; PrevIter != End; ++PrevIter) { + // If we broke the line the initial spaces are already + // accounted for + if (Changes[PrevIter->Index].NewlinesBefore > 0) + NetWidth = 0; + NetWidth += + calculateCellWidth(PrevIter->Index, PrevIter->EndIndex, true) + 1; + } + return NetWidth; + } + + /// Get the maximum width of a cell in a sequence of columns + template + unsigned getMaximumCellWidth(I CellIter, unsigned NetWidth) const { + unsigned CellWidth = + calculateCellWidth(CellIter->Index, CellIter->EndIndex, true); + if (Changes[CellIter->Index].NewlinesBefore == 0) + CellWidth += NetWidth; + for (const auto *Next = CellIter->NextColumnElement; Next != nullptr; + Next = Next->NextColumnElement) { + auto ThisWidth = calculateCellWidth(Next->Index, Next->EndIndex, true); + if (Changes[Next->Index].NewlinesBefore == 0) + ThisWidth += NetWidth; + CellWidth = std::max(CellWidth, ThisWidth); + } + return CellWidth; + } + + /// Get The maximum width of all columns to a given cell + template + unsigned getMaximumNetWidth(const I &CellStart, const I &CellStop, + unsigned InitialSpaces, + unsigned CellCount) const { + auto MaxNetWidth = getNetWidth(CellStart, CellStop, InitialSpaces); + auto RowCount = 1U; + auto Offset = std::distance(CellStart, CellStop); + for (const auto *Next = CellStop->NextColumnElement; Next != nullptr; + Next = Next->NextColumnElement) { + auto Start = (CellStart + RowCount * CellCount); + auto End = Start + Offset; + MaxNetWidth = + std::max(MaxNetWidth, getNetWidth(Start, End, InitialSpaces)); + ++RowCount; + } + return MaxNetWidth; + } + + /// align a split cell with a newline to the first element in the cell + void alignToStartOfCell(unsigned Start, unsigned End); + + /// Link the Cell pointers in the list of Cells + static CellDescriptions linkCells(CellDescriptions &&CellDesc); + /// Fill \c Replaces with the replacements for all effective changes. void generateChanges(); diff --git a/clang/lib/Format/WhitespaceManager.cpp b/clang/lib/Format/WhitespaceManager.cpp --- a/clang/lib/Format/WhitespaceManager.cpp +++ b/clang/lib/Format/WhitespaceManager.cpp @@ -13,6 +13,8 @@ #include "WhitespaceManager.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include namespace clang { namespace format { @@ -100,6 +102,7 @@ alignChainedConditionals(); alignTrailingComments(); alignEscapedNewlines(); + alignArrayInitializers(); generateChanges(); return Replaces; @@ -952,6 +955,305 @@ } } +void WhitespaceManager::alignArrayInitializers() { + if (Style.AlignArrayOfStructures == FormatStyle::AIAS_None) + return; + + for (unsigned ChangeIndex = 1U, ChangeEnd = Changes.size(); + ChangeIndex < ChangeEnd; ++ChangeIndex) { + auto &C = Changes[ChangeIndex]; + if (C.Tok->IsArrayInitializer) { + bool FoundComplete = false; + for (unsigned InsideIndex = ChangeIndex + 1; InsideIndex < ChangeEnd; + ++InsideIndex) { + if (Changes[InsideIndex].Tok == C.Tok->MatchingParen) { + alignArrayInitializers(ChangeIndex, InsideIndex + 1); + ChangeIndex = InsideIndex + 1; + FoundComplete = true; + break; + } + } + if (!FoundComplete) + ChangeIndex = ChangeEnd; + } + } +} + +void WhitespaceManager::alignArrayInitializers(unsigned Start, unsigned End) { + + if (Style.AlignArrayOfStructures == FormatStyle::AIAS_Right) + alignArrayInitializersRightJustified(getCells(Start, End)); + else if (Style.AlignArrayOfStructures == FormatStyle::AIAS_Left) + alignArrayInitializersLeftJustified(getCells(Start, End)); +} + +void WhitespaceManager::alignArrayInitializersRightJustified( + CellDescriptions &&CellDescs) { + auto &Cells = CellDescs.Cells; + + // Now go through and fixup the spaces + auto CellIter = Cells.begin(); + for (auto i = 0U; i < CellDescs.CellCount; i++, ++CellIter) { + unsigned NetWidth = 0U; + if (isSplitCell(*CellIter)) + NetWidth = getNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces); + auto CellWidth = getMaximumCellWidth(CellIter, NetWidth); + + if (Changes[CellIter->Index].Tok->is(tok::r_brace)) { + // So in here we want to see if there is a brace that falls + // on a line that was split. If so on that line we make sure that + // the spaces in front of the brace are enough + Changes[CellIter->Index].NewlinesBefore = 0; + Changes[CellIter->Index].Spaces = 0; + for (const auto *Next = CellIter->NextColumnElement; Next != nullptr; + Next = Next->NextColumnElement) { + Changes[Next->Index].Spaces = 0; + Changes[Next->Index].NewlinesBefore = 0; + } + // Except if the array is empty we need the position of all the + // cells immediantly adjacent to this + if (CellIter != Cells.begin()) { + auto ThisNetWidth = + getNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces); + auto MaxNetWidth = + getMaximumNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces, + CellDescs.CellCount); + if (ThisNetWidth < MaxNetWidth) + Changes[CellIter->Index].Spaces = (MaxNetWidth - ThisNetWidth); + auto RowCount = 1U; + auto Offset = std::distance(Cells.begin(), CellIter); + for (const auto *Next = CellIter->NextColumnElement; Next != nullptr; + Next = Next->NextColumnElement) { + auto Start = (Cells.begin() + RowCount * CellDescs.CellCount); + auto End = Start + Offset; + ThisNetWidth = getNetWidth(Start, End, CellDescs.InitialSpaces); + if (ThisNetWidth < MaxNetWidth) + Changes[Next->Index].Spaces = (MaxNetWidth - ThisNetWidth); + ++RowCount; + } + } + } else { + auto ThisWidth = + calculateCellWidth(CellIter->Index, CellIter->EndIndex, true) + + NetWidth; + if (Changes[CellIter->Index].NewlinesBefore == 0) { + Changes[CellIter->Index].Spaces = (CellWidth - (ThisWidth + NetWidth)); + Changes[CellIter->Index].Spaces += (i > 0) ? 1 : 0; + } + alignToStartOfCell(CellIter->Index, CellIter->EndIndex); + for (const auto *Next = CellIter->NextColumnElement; Next != nullptr; + Next = Next->NextColumnElement) { + ThisWidth = + calculateCellWidth(Next->Index, Next->EndIndex, true) + NetWidth; + if (Changes[Next->Index].NewlinesBefore == 0) { + Changes[Next->Index].Spaces = (CellWidth - ThisWidth); + Changes[Next->Index].Spaces += (i > 0) ? 1 : 0; + } + alignToStartOfCell(Next->Index, Next->EndIndex); + } + } + } +} + +void WhitespaceManager::alignArrayInitializersLeftJustified( + CellDescriptions &&CellDescs) { + auto &Cells = CellDescs.Cells; + + // Now go through and fixup the spaces + auto CellIter = Cells.begin(); + // The first cell needs to be against the left brace + if (Changes[CellIter->Index].NewlinesBefore == 0) + Changes[CellIter->Index].Spaces = 0; + else + Changes[CellIter->Index].Spaces = CellDescs.InitialSpaces; + ++CellIter; + for (auto i = 1U; i < CellDescs.CellCount; i++, ++CellIter) { + unsigned NetWidth = 0U; + if (isSplitCell(*CellIter)) + NetWidth = getNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces); + auto MaxNetWidth = getMaximumNetWidth( + Cells.begin(), CellIter, CellDescs.InitialSpaces, CellDescs.CellCount); + auto ThisNetWidth = + getNetWidth(Cells.begin(), CellIter, CellDescs.InitialSpaces); + if (Changes[CellIter->Index].NewlinesBefore == 0) { + Changes[CellIter->Index].Spaces = + MaxNetWidth - ThisNetWidth + + (Changes[CellIter->Index].Tok->isNot(tok::r_brace) ? 1 : 0); + } + auto RowCount = 1U; + auto Offset = std::distance(Cells.begin(), CellIter); + for (const auto *Next = CellIter->NextColumnElement; Next != nullptr; + Next = Next->NextColumnElement) { + auto Start = (Cells.begin() + RowCount * CellDescs.CellCount); + auto End = Start + Offset; + auto ThisNetWidth = getNetWidth(Start, End, CellDescs.InitialSpaces); + if (Changes[Next->Index].NewlinesBefore == 0) { + Changes[Next->Index].Spaces = + MaxNetWidth - ThisNetWidth + + (Changes[Next->Index].Tok->isNot(tok::r_brace) ? 1 : 0); + } + ++RowCount; + } + } +} + +bool WhitespaceManager::isSplitCell(const CellDescription &Cell) { + if (Cell.HasSplit) + return true; + for (const auto *Next = Cell.NextColumnElement; Next != nullptr; + Next = Next->NextColumnElement) { + if (Next->HasSplit) + return true; + } + return false; +} + +WhitespaceManager::CellDescriptions WhitespaceManager::getCells(unsigned Start, + unsigned End) { + + unsigned Depth = 0; + unsigned Cell = 0; + unsigned CellCount = 0; + unsigned InitialSpaces = 0; + unsigned InitialTokenLength = 0; + unsigned EndSpaces = 0; + SmallVector Cells; + const FormatToken *MatchingParen = nullptr; + for (unsigned i = Start; i < End; ++i) { + auto &C = Changes[i]; + if (C.Tok->is(tok::l_brace)) + ++Depth; + else if (C.Tok->is(tok::r_brace)) + --Depth; + if (Depth == 2) { + if (C.Tok->is(tok::l_brace)) { + Cell = 0; + MatchingParen = C.Tok->MatchingParen; + if (InitialSpaces == 0) { + InitialSpaces = C.Spaces + C.TokenLength; + InitialTokenLength = C.TokenLength; + auto j = i - 1; + for (; Changes[j].NewlinesBefore == 0 && j > Start; --j) { + InitialSpaces += Changes[j].Spaces + Changes[j].TokenLength; + InitialTokenLength += Changes[j].TokenLength; + } + if (C.NewlinesBefore == 0) { + InitialSpaces += Changes[j].Spaces + Changes[j].TokenLength; + InitialTokenLength += Changes[j].TokenLength; + } + } + } else if (C.Tok->is(tok::comma)) { + if (!Cells.empty()) + Cells.back().EndIndex = i; + Cell++; + } + } else if (Depth == 1) { + if (C.Tok == MatchingParen) { + if (!Cells.empty()) + Cells.back().EndIndex = i; + Cells.push_back(CellDescription{i, ++Cell, i + 1, false, nullptr}); + CellCount = Cell + 1; + // Go to the next non-comment and ensure there is a break in front + const auto *NextNonComment = C.Tok->getNextNonComment(); + while (NextNonComment->is(tok::comma)) + NextNonComment = NextNonComment->getNextNonComment(); + auto j = i; + while (Changes[j].Tok != NextNonComment && j < End) + j++; + if (j < End && Changes[j].NewlinesBefore == 0 && + Changes[j].Tok->isNot(tok::r_brace)) { + Changes[j].NewlinesBefore = 1; + // Account for the added token lengths + Changes[j].Spaces = InitialSpaces - InitialTokenLength; + } + } else if (C.Tok->is(tok::comment)) { + // Trailing comments stay at a space past the last token + C.Spaces = Changes[i - 1].Tok->is(tok::comma) ? 1 : 2; + } else if (C.Tok->is(tok::l_brace)) { + // We need to make sure that the ending braces is aligned to the + // start of our initializer + auto j = i - 1; + for (; j > 0 && !Changes[j].Tok->ArrayInitializerLineStart; --j) + ; // Nothing the loop does the work + EndSpaces = Changes[j].Spaces; + } + } else if (Depth == 0 && C.Tok->is(tok::r_brace)) { + C.NewlinesBefore = 1; + C.Spaces = EndSpaces; + } + if (C.Tok->StartsColumn) { + // This gets us past tokens that have been split over multiple + // lines + bool HasSplit = false; + if (Changes[i].NewlinesBefore > 0) { + // So if we split a line previously and the tail line + this token is + // less then the column limit we remove the split here and just put + // the column start at a space past the comma + auto j = i - 1; + if ((j - 1) > Start && Changes[j].Tok->is(tok::comma) && + Changes[j - 1].NewlinesBefore > 0) { + --j; + auto LineLimit = Changes[j].Spaces + Changes[j].TokenLength; + if (LineLimit < Style.ColumnLimit) { + Changes[i].NewlinesBefore = 0; + Changes[i].Spaces = 1; + } + } + } + while (Changes[i].NewlinesBefore > 0 && Changes[i].Tok == C.Tok) { + Changes[i].Spaces = InitialSpaces; + ++i; + HasSplit = true; + } + if (Changes[i].Tok != C.Tok) + --i; + Cells.push_back(CellDescription{i, Cell, i, HasSplit, nullptr}); + } + } + + return linkCells({Cells, CellCount, InitialSpaces}); +} + +unsigned WhitespaceManager::calculateCellWidth(unsigned Start, unsigned End, + bool WithSpaces) const { + unsigned CellWidth = 0; + for (auto i = Start; i < End; i++) { + if (Changes[i].NewlinesBefore > 0) + CellWidth = 0; + CellWidth += Changes[i].TokenLength; + CellWidth += (WithSpaces ? Changes[i].Spaces : 0); + } + return CellWidth; +} + +void WhitespaceManager::alignToStartOfCell(unsigned Start, unsigned End) { + if ((End - Start) <= 1) + return; + // If the line is broken anywhere in there make sure everything + // is aligned to the parent + for (auto i = Start + 1; i < End; i++) { + if (Changes[i].NewlinesBefore > 0) + Changes[i].Spaces = Changes[Start].Spaces; + } +} + +WhitespaceManager::CellDescriptions +WhitespaceManager::linkCells(CellDescriptions &&CellDesc) { + auto &Cells = CellDesc.Cells; + for (auto CellIter = Cells.begin(); CellIter != Cells.end(); ++CellIter) { + if (CellIter->NextColumnElement == nullptr && + ((CellIter + 1) != Cells.end())) { + for (auto NextIter = CellIter + 1; NextIter != Cells.end(); ++NextIter) { + if (NextIter->Cell == CellIter->Cell) { + CellIter->NextColumnElement = &(*NextIter); + break; + } + } + } + } + return std::move(CellDesc); +} + void WhitespaceManager::generateChanges() { for (unsigned i = 0, e = Changes.size(); i != e; ++i) { const Change &C = Changes[i]; diff --git a/clang/test/Format/struct-array-initializer-left.cpp b/clang/test/Format/struct-array-initializer-left.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Format/struct-array-initializer-left.cpp @@ -0,0 +1,36 @@ +// RUN: grep -Ev "// *[A-Z-]+:" %s \ +// RUN: | clang-format -style="{BasedOnStyle: LLVM, AlignArrayOfStructures: Left}" %s \ +// RUN: | FileCheck -strict-whitespace %s +struct test { + int a; + int b; + const char *c +}; + +struct toast { + int a; + const char *b; + int c; + float d; +}; + +void f() { + struct test demo[] = {{56, 23, "hello"}, {-1, 93463, "world"}, {7, 5, "!!"}}; + // CHECK: {{^[[:space:]]{2}struct test demo\[\] = \{$}} + // CHECK-NEXT: {{([[:space:]]{4})}}{56, 23, "hello"}, + // CHECK-NEXT: {{([[:space:]]{4})}}{-1, 93463, "world"}, + // CHECK-NEXT: {{([[:space:]]{4})}}{7, 5, "!!" } + // CHECK-NEXT: {{^[[:space:]]{2}\};$}} +} + +void g() { + struct toast demo[] = { + {56, "hello world I have some things to say", 30, 4.2}, + {93463, "those things are really comments", 1, 3.1}, + {7, "about a wide range of topics", 789, .112233}}; + // CHECK: {{^[[:space:]]{2}struct toast demo\[\] = \{$}} + // CHECK-NEXT: {{([[:space:]]{4})}}{56, "hello world I have some things to say", 30, 4.2 }, + // CHECK-NEXT: {{([[:space:]]{4})}}{93463, "those things are really comments", 1, 3.1 }, + // CHECK-NEXT: {{([[:space:]]{4})}}{7, "about a wide range of topics", 789, .112233} + // CHECK-NEXT: {{^[[:space:]]{2}\};$}} +} diff --git a/clang/test/Format/struct-array-initializer-right.cpp b/clang/test/Format/struct-array-initializer-right.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Format/struct-array-initializer-right.cpp @@ -0,0 +1,36 @@ +// RUN: grep -Ev "// *[A-Z-]+:" %s \ +// RUN: | clang-format -style="{BasedOnStyle: LLVM, AlignArrayOfStructures: Right}" %s \ +// RUN: | FileCheck -strict-whitespace %s +struct test { + int a; + int b; + const char *c +}; + +struct toast { + int a; + const char *b; + int c; + float d; +}; + +void f() { + struct test demo[] = {{56, 23, "hello"}, {-1, 93463, "world"}, {7, 5, "!!"}}; + // CHECK: {{^[[:space:]]{2}struct test demo\[\] = \{$}} + // CHECK-NEXT: {{([[:space:]]{4})}}{56, 23, "hello"}, + // CHECK-NEXT: {{([[:space:]]{4})}}{-1, 93463, "world"}, + // CHECK-NEXT: {{([[:space:]]{4})}}{ 7, 5, "!!"} + // CHECK-NEXT: {{^[[:space:]]{2}\};$}} +} + +void g() { + struct toast demo[] = { + {56, "hello world I have some things to say", 30, 4.2}, + {93463, "those things are really comments", 1, 3.1}, + {7, "about a wide range of topics", 789, .112233}}; + // CHECK: {{^[[:space:]]{2}struct toast demo\[\] = \{$}} + // CHECK-NEXT: {{([[:space:]]{4})}}{ 56, "hello world I have some things to say", 30, 4.2}, + // CHECK-NEXT: {{([[:space:]]{4})}}{93463, "those things are really comments", 1, 3.1}, + // CHECK-NEXT: {{([[:space:]]{4})}}{ 7, "about a wide range of topics", 789, .112233} + // CHECK-NEXT: {{^[[:space:]]{2}\};$}} +} 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 @@ -16602,6 +16602,407 @@ getLLVMStyle()); } +TEST_F(FormatTest, CatchAlignArrayOfStructuresRightAlignment) { + auto Style = getLLVMStyle(); + Style.AlignArrayOfStructures = FormatStyle::AIAS_Right; + Style.AlignConsecutiveAssignments = + FormatStyle::AlignConsecutiveStyle::ACS_Consecutive; + Style.AlignConsecutiveDeclarations = + FormatStyle::AlignConsecutiveStyle::ACS_Consecutive; + verifyFormat("struct test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " { 7, 5, \"!!\"}\n" + "};\n", + Style); + + verifyFormat("struct test demo[] = {\n" + " {56, 23, \"hello\"}, // first line\n" + " {-1, 93463, \"world\"}, // second line\n" + " { 7, 5, \"!!\"} // third line\n" + "};\n", + Style); + + verifyFormat("struct test demo[4] = {\n" + " { 56, 23, 21, \"oh\"}, // first line\n" + " { -1, 93463, 22, \"my\"}, // second line\n" + " { 7, 5, 1, \"goodness\"} // third line\n" + " {234, 5, 1, \"gracious\"} // fourth line\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); + + verifyFormat("struct test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " { 7, 5, \"!!\"},\n" + "};\n", + Style); + + verifyFormat("test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " { 7, 5, \"!!\"},\n" + "};\n", + Style); + + verifyFormat("demo = std::array{\n" + " test{56, 23, \"hello\"},\n" + " test{-1, 93463, \"world\"},\n" + " test{ 7, 5, \"!!\"},\n" + "};\n", + Style); + + verifyFormat("test demo[] = {\n" + " {56, 23, \"hello\"},\n" + "#if X\n" + " {-1, 93463, \"world\"},\n" + "#endif\n" + " { 7, 5, \"!!\"}\n" + "};\n", + Style); + + verifyFormat( + "test demo[] = {\n" + " { 7, 23,\n" + " \"hello world i am a very long line that really, in any\"\n" + " \"just world, ought to be split over multiple lines\"},\n" + " {-1, 93463, \"world\"},\n" + " {56, 5, \"!!\"}\n" + "};\n", + Style); + + verifyFormat("return GradForUnaryCwise(g, {\n" + " {{\"sign\"}, \"Sign\", " + " {\"x\", \"dy\"}},\n" + " { {\"dx\"}, \"Mul\", {\"dy\"" + ", \"sign\"}},\n" + "});\n", + Style); + + Style.ColumnLimit = 0; + EXPECT_EQ( + "test demo[] = {\n" + " {56, 23, \"hello world i am a very long line that really, " + "in any just world, ought to be split over multiple lines\"},\n" + " {-1, 93463, " + " \"world\"},\n" + " { 7, 5, " + " \"!!\"},\n" + "};", + format("test demo[] = {{56, 23, \"hello world i am a very long line " + "that really, in any just world, ought to be split over multiple " + "lines\"},{-1, 93463, \"world\"},{7, 5, \"!!\"},};", + Style)); + + Style.ColumnLimit = 80; + verifyFormat("test demo[] = {\n" + " {56, 23, /* a comment */ \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " { 7, 5, \"!!\"}\n" + "};\n", + Style); + + verifyFormat("test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\" /* comment here */},\n" + " { 7, 5, \"!!\"}\n" + "};\n", + Style); + + verifyFormat("test demo[] = {\n" + " {56, /* a comment */ 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " { 7, 5, \"!!\"}\n" + "};\n", + Style); + + Style.ColumnLimit = 20; + EXPECT_EQ( + "demo = std::array<\n" + " struct test, 3>{\n" + " test{\n" + " 56, 23,\n" + " \"hello \"\n" + " \"world i \"\n" + " \"am a very \"\n" + " \"long line \"\n" + " \"that \"\n" + " \"really, \"\n" + " \"in any \"\n" + " \"just \"\n" + " \"world, \"\n" + " \"ought to \"\n" + " \"be split \"\n" + " \"over \"\n" + " \"multiple \"\n" + " \"lines\"},\n" + " test{-1, 93463,\n" + " \"world\"},\n" + " test{ 7, 5,\n" + " \"!!\" },\n" + "};", + format("demo = std::array{test{56, 23, \"hello world " + "i am a very long line that really, in any just world, ought " + "to be split over multiple lines\"},test{-1, 93463, \"world\"}," + "test{7, 5, \"!!\"},};", + Style)); + // This caused a core dump by enabling Alignment in the LLVMStyle globally + Style = getLLVMStyleWithColumns(50); + Style.AlignArrayOfStructures = FormatStyle::AIAS_Right; + verifyFormat("static A x = {\n" + " {{init1, init2, init3, init4},\n" + " {init1, init2, init3, init4}}\n" + "};", + Style); + Style.ColumnLimit = 100; + EXPECT_EQ( + "test demo[] = {\n" + " {56, 23,\n" + " \"hello world i am a very long line that really, in any just world" + ", ought to be split over \"\n" + " \"multiple lines\" },\n" + " {-1, 93463, \"world\"},\n" + " { 7, 5, \"!!\"},\n" + "};", + format("test demo[] = {{56, 23, \"hello world i am a very long line " + "that really, in any just world, ought to be split over multiple " + "lines\"},{-1, 93463, \"world\"},{7, 5, \"!!\"},};", + Style)); + + Style = getLLVMStyleWithColumns(50); + Style.AlignArrayOfStructures = FormatStyle::AIAS_Right; + Style.AlignConsecutiveAssignments = + FormatStyle::AlignConsecutiveStyle::ACS_Consecutive; + Style.AlignConsecutiveDeclarations = + FormatStyle::AlignConsecutiveStyle::ACS_Consecutive; + verifyFormat("struct test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " { 7, 5, \"!!\"}\n" + "};\n" + "static A x = {\n" + " {{init1, init2, init3, init4},\n" + " {init1, init2, init3, init4}}\n" + "};", + Style); + Style.ColumnLimit = 100; + Style.AlignConsecutiveAssignments = + FormatStyle::AlignConsecutiveStyle::ACS_AcrossComments; + Style.AlignConsecutiveDeclarations = + FormatStyle::AlignConsecutiveStyle::ACS_AcrossComments; + verifyFormat("struct test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " { 7, 5, \"!!\"}\n" + "};\n" + "struct test demo[4] = {\n" + " { 56, 23, 21, \"oh\"}, // first line\n" + " { -1, 93463, 22, \"my\"}, // second line\n" + " { 7, 5, 1, \"goodness\"} // third line\n" + " {234, 5, 1, \"gracious\"} // fourth line\n" + "};\n", + Style); + EXPECT_EQ( + "test demo[] = {\n" + " {56,\n" + " \"hello world i am a very long line that really, in any just world" + ", ought to be split over \"\n" + " \"multiple lines\", 23},\n" + " {-1, \"world\", 93463},\n" + " { 7, \"!!\", 5},\n" + "};", + format("test demo[] = {{56, \"hello world i am a very long line " + "that really, in any just world, ought to be split over multiple " + "lines\", 23},{-1, \"world\", 93463},{7, \"!!\", 5},};", + Style)); +} + +TEST_F(FormatTest, CatchAlignArrayOfStructuresLeftAlignment) { + auto Style = getLLVMStyle(); + Style.AlignArrayOfStructures = FormatStyle::AIAS_Left; + verifyFormat("struct test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " {7, 5, \"!!\" }\n" + "};\n", + Style); + + verifyFormat("struct test demo[] = {\n" + " {56, 23, \"hello\"}, // first line\n" + " {-1, 93463, \"world\"}, // second line\n" + " {7, 5, \"!!\" } // third line\n" + "};\n", + Style); + verifyFormat("struct test demo[4] = {\n" + " {56, 23, 21, \"oh\" }, // first line\n" + " {-1, 93463, 22, \"my\" }, // second line\n" + " {7, 5, 1, \"goodness\"} // third line\n" + " {234, 5, 1, \"gracious\"} // fourth line\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); + verifyFormat("struct test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " {7, 5, \"!!\" },\n" + "};\n", + Style); + verifyFormat("test demo[] = {\n" + " {56, 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " {7, 5, \"!!\" },\n" + "};\n", + Style); + verifyFormat("demo = std::array{\n" + " test{56, 23, \"hello\"},\n" + " test{-1, 93463, \"world\"},\n" + " test{7, 5, \"!!\" },\n" + "};\n", + Style); + verifyFormat("test demo[] = {\n" + " {56, 23, \"hello\"},\n" + "#if X\n" + " {-1, 93463, \"world\"},\n" + "#endif\n" + " {7, 5, \"!!\" }\n" + "};\n", + Style); + verifyFormat( + "test demo[] = {\n" + " {7, 23,\n" + " \"hello world i am a very long line that really, in any\"\n" + " \"just world, ought to be split over multiple lines\"},\n" + " {-1, 93463, \"world\" },\n" + " {56, 5, \"!!\" }\n" + "};\n", + Style); + + verifyFormat("return GradForUnaryCwise(g, {\n" + " {{\"sign\"}, \"Sign\", {\"x\", " + "\"dy\"} },\n" + " {{\"dx\"}, \"Mul\", " + "{\"dy\", \"sign\"}},\n" + "});\n", + Style); + + Style.ColumnLimit = 0; + EXPECT_EQ( + "test demo[] = {\n" + " {56, 23, \"hello world i am a very long line that really, in any " + "just world, ought to be split over multiple lines\"},\n" + " {-1, 93463, \"world\" " + " },\n" + " {7, 5, \"!!\" " + " },\n" + "};", + format("test demo[] = {{56, 23, \"hello world i am a very long line " + "that really, in any just world, ought to be split over multiple " + "lines\"},{-1, 93463, \"world\"},{7, 5, \"!!\"},};", + Style)); + + Style.ColumnLimit = 80; + verifyFormat("test demo[] = {\n" + " {56, 23, /* a comment */ \"hello\"},\n" + " {-1, 93463, \"world\" },\n" + " {7, 5, \"!!\" }\n" + "};\n", + Style); + + verifyFormat("test demo[] = {\n" + " {56, 23, \"hello\" },\n" + " {-1, 93463, \"world\" /* comment here */},\n" + " {7, 5, \"!!\" }\n" + "};\n", + Style); + + verifyFormat("test demo[] = {\n" + " {56, /* a comment */ 23, \"hello\"},\n" + " {-1, 93463, \"world\"},\n" + " {7, 5, \"!!\" }\n" + "};\n", + Style); + + Style.ColumnLimit = 20; + EXPECT_EQ( + "demo = std::array<\n" + " struct test, 3>{\n" + " test{\n" + " 56, 23,\n" + " \"hello \"\n" + " \"world i \"\n" + " \"am a very \"\n" + " \"long line \"\n" + " \"that \"\n" + " \"really, \"\n" + " \"in any \"\n" + " \"just \"\n" + " \"world, \"\n" + " \"ought to \"\n" + " \"be split \"\n" + " \"over \"\n" + " \"multiple \"\n" + " \"lines\"},\n" + " test{-1, 93463,\n" + " \"world\"},\n" + " test{7, 5,\n" + " \"!!\" },\n" + "};", + format("demo = std::array{test{56, 23, \"hello world " + "i am a very long line that really, in any just world, ought " + "to be split over multiple lines\"},test{-1, 93463, \"world\"}," + "test{7, 5, \"!!\"},};", + Style)); + + // This caused a core dump by enabling Alignment in the LLVMStyle globally + Style = getLLVMStyleWithColumns(50); + Style.AlignArrayOfStructures = FormatStyle::AIAS_Left; + verifyFormat("static A x = {\n" + " {{init1, init2, init3, init4},\n" + " {init1, init2, init3, init4}}\n" + "};", + Style); + Style.ColumnLimit = 100; + EXPECT_EQ( + "test demo[] = {\n" + " {56, 23,\n" + " \"hello world i am a very long line that really, in any just world" + ", ought to be split over \"\n" + " \"multiple lines\" },\n" + " {-1, 93463, \"world\"},\n" + " {7, 5, \"!!\" },\n" + "};", + format("test demo[] = {{56, 23, \"hello world i am a very long line " + "that really, in any just world, ought to be split over multiple " + "lines\"},{-1, 93463, \"world\"},{7, 5, \"!!\"},};", + Style)); +} + TEST_F(FormatTest, UnderstandsPragmas) { verifyFormat("#pragma omp reduction(| : var)"); verifyFormat("#pragma omp reduction(+ : var)");