startsExternCBlock(
constAnnotatedLine &
Line) {
24 constFormatToken *Next =
Line.First->getNextNonComment();
25 constFormatToken *NextNext = Next ? Next->getNextNonComment() :
nullptr;
26 return Line.startsWith(tok::kw_extern) && Next && Next->isStringLiteral() &&
27NextNext && NextNext->is(tok::l_brace);
30boolisRecordLBrace(
constFormatToken &Tok) {
31 returnTok.isOneOf(TT_ClassLBrace, TT_EnumLBrace, TT_RecordLBrace,
32TT_StructLBrace, TT_UnionLBrace);
44classLevelIndentTracker {
46LevelIndentTracker(
constFormatStyle &Style,
47 constAdditionalKeywords &Keywords,
unsignedStartLevel,
49: Style(Style), Keywords(Keywords), AdditionalIndent(AdditionalIndent) {
50 for(
unsignedi = 0; i != StartLevel; ++i)
51IndentForLevel.push_back(Style.IndentWidth * i + AdditionalIndent);
55 unsignedgetIndent()
const{
returnIndent; }
59 voidnextLine(
constAnnotatedLine &
Line) {
60Offset = getIndentOffset(
Line);
63 if(
Line.Level >= IndentForLevel.size())
64IndentForLevel.resize(
Line.Level + 1, -1);
66(
Line.InPPDirective ||
69 unsignedPPIndentWidth =
70(Style.PPIndentWidth >= 0) ? Style.PPIndentWidth : Style.IndentWidth;
71Indent =
Line.InMacroBody
72?
Line.PPLevel * PPIndentWidth +
73(
Line.Level -
Line.PPLevel) * Style.IndentWidth
74:
Line.Level * PPIndentWidth;
75Indent += AdditionalIndent;
80 if(!
Line.InPPDirective) {
81assert(
Line.Level <= IndentForLevel.size());
82IndentForLevel.resize(
Line.Level + 1);
84Indent = getIndent(
Line.Level);
86 if(
static_cast<int>(Indent) + Offset >= 0)
88 if(
Line.IsContinuation)
89Indent =
Line.Level * Style.IndentWidth + Style.ContinuationIndentWidth;
97 voidadjustToUnmodifiedLine(
constAnnotatedLine &
Line) {
98 if(
Line.InPPDirective ||
Line.IsContinuation)
100assert(
Line.Level < IndentForLevel.size());
101 if(
Line.First->is(tok::comment) && IndentForLevel[
Line.Level] != -1)
103 unsignedLevelIndent =
Line.First->OriginalColumn;
104 if(
static_cast<int>(LevelIndent) - Offset >= 0)
105LevelIndent -= Offset;
106IndentForLevel[
Line.Level] = LevelIndent;
114 intgetIndentOffset(
constAnnotatedLine &
Line) {
120 autoIsAccessModifier = [&](
constFormatToken &RootToken) {
124 const auto*Next = RootToken.Next;
127 if(RootToken.isOneOf(Keywords.kw_signals, Keywords.kw_qsignals) &&
128Next && Next->is(tok::colon)) {
132 if(Next && Next->isOneOf(Keywords.kw_slots, Keywords.kw_qslots) &&
133Next->Next && Next->Next->is(tok::colon)) {
138 return!Next && RootToken.isAccessSpecifier(
false);
141 if(IsAccessModifier(*
Line.First)) {
145 returnStyle.IndentAccessModifiers ? -Style.IndentWidth
146: Style.AccessModifierOffset;
157 unsignedgetIndent(
unsignedLevel)
const{
158assert(Level < IndentForLevel.size());
159 if(IndentForLevel[Level] != -1)
160 returnIndentForLevel[Level];
163 returngetIndent(Level - 1) + Style.IndentWidth;
166 constFormatStyle &Style;
167 constAdditionalKeywords &Keywords;
168 const unsignedAdditionalIndent;
174SmallVector<int> IndentForLevel;
187getMatchingNamespaceToken(
constAnnotatedLine *
Line,
188 constArrayRef<AnnotatedLine *> &AnnotatedLines) {
189 if(!
Line->startsWith(tok::r_brace))
191 size_tStartLineIndex =
Line->MatchingOpeningBlockLineIndex;
194assert(StartLineIndex < AnnotatedLines.size());
195 returnAnnotatedLines[StartLineIndex]->First->getNamespaceToken();
199 constFormatToken *NamespaceToken =
Line->First->getNamespaceToken();
200 returnNamespaceToken ? NamespaceToken->TokenText : StringRef();
204getMatchingNamespaceTokenText(
constAnnotatedLine *
Line,
205 constArrayRef<AnnotatedLine *> &AnnotatedLines) {
206 constFormatToken *NamespaceToken =
207getMatchingNamespaceToken(
Line, AnnotatedLines);
208 returnNamespaceToken ? NamespaceToken->TokenText : StringRef();
213LineJoiner(
constFormatStyle &Style,
constAdditionalKeywords &Keywords,
214 constSmallVectorImpl<AnnotatedLine *> &Lines)
215: Style(Style), Keywords(Keywords), End(Lines.end()), Next(Lines.begin()),
216AnnotatedLines(Lines) {}
219 constAnnotatedLine *getNextMergedLine(
boolDryRun,
220LevelIndentTracker &IndentTracker) {
223 constAnnotatedLine *Current = *Next;
224IndentTracker.nextLine(*Current);
225 unsignedMergedLines = tryFitMultipleLinesInOne(IndentTracker, Next, End);
226 if(MergedLines > 0 && Style.ColumnLimit == 0) {
229 for(
unsignedi = 0; i < MergedLines; ++i)
230 if(Next[i + 1]->
First->NewlinesBefore > 0)
234 for(
unsignedi = 0; i < MergedLines; ++i)
235join(*Next[0], *Next[i + 1]);
236Next = Next + MergedLines + 1;
243tryFitMultipleLinesInOne(LevelIndentTracker &IndentTracker,
244ArrayRef<AnnotatedLine *>::const_iterator I,
245ArrayRef<AnnotatedLine *>::const_iterator
E) {
246 const unsignedIndent = IndentTracker.getIndent();
252 constAnnotatedLine *TheLine = *I;
253 if(TheLine->Last->is(TT_LineComment))
255 const auto&NextLine = *I[1];
256 if(NextLine.Type ==
LT_Invalid|| NextLine.First->MustBreakBefore)
258 if(TheLine->InPPDirective &&
259(!NextLine.InPPDirective || NextLine.First->HasUnescapedNewline)) {
263 if(Style.ColumnLimit > 0 && Indent > Style.ColumnLimit)
267Style.ColumnLimit == 0 ?
UINT_MAX: Style.ColumnLimit - Indent;
270Limit = TheLine->Last->TotalLength > Limit
272: Limit - TheLine->Last->TotalLength;
274 if(TheLine->Last->is(TT_FunctionLBrace) &&
275TheLine->First == TheLine->Last &&
276!Style.BraceWrapping.SplitEmptyFunction &&
277NextLine.First->is(tok::r_brace)) {
278 returntryMergeSimpleBlock(I,
E, Limit);
281 const auto*PreviousLine = I != AnnotatedLines.begin() ? I[-1] :
nullptr;
283 if(PreviousLine && TheLine->Last->is(tok::l_brace) &&
284TheLine->First == TheLine->Last) {
285 boolEmptyBlock = NextLine.First->is(tok::r_brace);
287 constFormatToken *Tok = PreviousLine->First;
288 if(Tok && Tok->is(tok::comment))
289Tok = Tok->getNextNonComment();
291 if(Tok && Tok->getNamespaceToken()) {
292 return!Style.BraceWrapping.SplitEmptyNamespace && EmptyBlock
293? tryMergeSimpleBlock(I,
E, Limit)
297 if(Tok && Tok->is(tok::kw_typedef))
298Tok = Tok->getNextNonComment();
299 if(Tok && Tok->isOneOf(tok::kw_class, tok::kw_struct, tok::kw_union,
300tok::kw_extern, Keywords.kw_interface)) {
301 return!Style.BraceWrapping.SplitEmptyRecord && EmptyBlock
302? tryMergeSimpleBlock(I,
E, Limit)
306 if(Tok && Tok->is(tok::kw_template) &&
307Style.BraceWrapping.SplitEmptyRecord && EmptyBlock) {
312 autoShouldMergeShortFunctions = [
this, &I, &NextLine, PreviousLine,
317NextLine.First->is(tok::r_brace)) {
321 if(Style.AllowShortFunctionsOnASingleLine &
325 if(Style.isJavaScript() && TheLine->Last->is(TT_FunctionLBrace))
328 if(TheLine->Level != 0) {
334 constAnnotatedLine *
Line=
nullptr;
335 for(
autoJ = I - 1; J >= AnnotatedLines.begin(); --J) {
337 if(!(*J)->InPPDirective && !(*J)->isComment() &&
338(*J)->Level < TheLine->Level) {
348 const auto*LastNonComment =
Line->getLastNonComment();
351assert(LastNonComment);
352 returnisRecordLBrace(*LastNonComment);
359 boolMergeShortFunctions = ShouldMergeShortFunctions();
361 const auto*FirstNonComment = TheLine->getFirstNonComment();
362 if(!FirstNonComment)
368 if(Style.AllowShortNamespacesOnASingleLine &&
369TheLine->First->is(tok::kw_namespace) &&
370TheLine->Last->is(tok::l_brace)) {
371 const autoresult = tryMergeNamespace(I,
E, Limit);
376 if(Style.CompactNamespaces) {
377 if(
const auto*NSToken = TheLine->First->getNamespaceToken()) {
379assert(TheLine->MatchingClosingBlockLineIndex > 0);
380 for(
autoClosingLineIndex = TheLine->MatchingClosingBlockLineIndex - 1;
382ClosingLineIndex == I[J]->MatchingClosingBlockLineIndex &&
383I[J]->
Last->TotalLength < Limit;
384++J, --ClosingLineIndex) {
385Limit -= I[J]->Last->TotalLength + 1;
389 auto*ClosingLine = AnnotatedLines.begin() + ClosingLineIndex + 1;
390 const intOutdentBy = I[J]->Level - TheLine->Level;
391assert(OutdentBy >= 0);
392 for(
auto*CompactedLine = I + J; CompactedLine <= ClosingLine;
394 if(!(*CompactedLine)->InPPDirective) {
395 const intLevel = (*CompactedLine)->Level;
396(*CompactedLine)->Level = std::max(Level - OutdentBy, 0);
403 if(
autonsToken = getMatchingNamespaceToken(TheLine, AnnotatedLines)) {
405 unsignedopeningLine = TheLine->MatchingOpeningBlockLineIndex - 1;
406 for(; I + 1 + i !=
E&&
407nsToken->TokenText ==
408getMatchingNamespaceTokenText(I[i + 1], AnnotatedLines) &&
409openingLine == I[i + 1]->MatchingOpeningBlockLineIndex;
410i++, --openingLine) {
412I[i + 1]->First->SpacesRequiredBefore =
413I[i]->Last->isNot(tok::r_brace);
416IndentTracker.nextLine(*I[i + 1]);
422 const auto*LastNonComment = TheLine->getLastNonComment();
423assert(LastNonComment);
428 if(LastNonComment->is(TT_FunctionLBrace) &&
429TheLine->First != LastNonComment) {
430 returnMergeShortFunctions ? tryMergeSimpleBlock(I,
E, Limit) : 0;
434 if(TheLine->Last->is(tok::l_brace) && FirstNonComment != TheLine->Last &&
435(FirstNonComment->isOneOf(tok::kw_if, tok::kw_while, tok::kw_for,
437TheLine->startsWithExportBlock())) {
439? tryMergeSimpleBlock(I,
E, Limit)
443 if(NextLine.First->is(tok::l_brace)) {
444 if((TheLine->First->isOneOf(tok::kw_if, tok::kw_else, tok::kw_while,
445tok::kw_for, tok::kw_switch, tok::kw_try,
446tok::kw_do, TT_ForEachMacro) ||
447(TheLine->First->is(tok::r_brace) && TheLine->First->Next &&
448TheLine->First->Next->isOneOf(tok::kw_else, tok::kw_catch))) &&
449Style.BraceWrapping.AfterControlStatement ==
454 return(Style.ColumnLimit == 0 || TheLine->Level * Style.IndentWidth +
455TheLine->Last->TotalLength <=
460 if(TheLine->First->isOneOf(tok::kw_if, tok::kw_else, tok::kw_while,
461tok::kw_for, TT_ForEachMacro)) {
462 return(Style.BraceWrapping.AfterControlStatement ==
464? tryMergeSimpleBlock(I,
E, Limit)
467 if(TheLine->First->isOneOf(tok::kw_else, tok::kw_catch) &&
468Style.BraceWrapping.AfterControlStatement ==
475 return(Style.ColumnLimit == 0 ||
476TheLine->Last->TotalLength <= Style.ColumnLimit)
481 if(PreviousLine && TheLine->First->is(tok::l_brace)) {
482 switch(PreviousLine->First->Tok.getKind()) {
485 if(PreviousLine->First->Next) {
487PreviousLine->First->Next->Tok.getObjCKeywordID();
488 if(kwId == tok::objc_autoreleasepool ||
489kwId == tok::objc_synchronized) {
496 casetok::kw_default:
507 if(PreviousLine && Style.BraceWrapping.SplitEmptyRecord &&
508TheLine->Last->is(tok::l_brace) && PreviousLine->Last) {
509 constFormatToken *
Previous= PreviousLine->Last;
514 if(
Previous->is(tok::greater) && !PreviousLine->InPPDirective)
516 if(
Previous->is(tok::identifier)) {
517 constFormatToken *PreviousPrevious =
519 if(PreviousPrevious &&
520PreviousPrevious->isOneOf(tok::kw_class, tok::kw_struct)) {
528 if(TheLine->First->is(TT_SwitchExpressionLabel)) {
529 returnStyle.AllowShortCaseExpressionOnASingleLine
530? tryMergeShortCaseLabels(I,
E, Limit)
534 if(TheLine->Last->is(tok::l_brace)) {
535 boolShouldMerge =
false;
537 if(TheLine->Last->is(TT_EnumLBrace)) {
538ShouldMerge = Style.AllowShortEnumsOnASingleLine;
539}
else if(TheLine->Last->is(TT_CompoundRequirementLBrace)) {
540ShouldMerge = Style.AllowShortCompoundRequirementOnASingleLine;
541}
else if(TheLine->Last->isOneOf(TT_ClassLBrace, TT_StructLBrace)) {
545ShouldMerge = !Style.BraceWrapping.AfterClass ||
546(NextLine.First->is(tok::r_brace) &&
547!Style.BraceWrapping.SplitEmptyRecord);
548}
else if(TheLine->InPPDirective ||
549!TheLine->First->isOneOf(tok::kw_class, tok::kw_enum,
553ShouldMerge = !Style.BraceWrapping.AfterFunction ||
554(NextLine.First->is(tok::r_brace) &&
555!Style.BraceWrapping.SplitEmptyFunction);
557 returnShouldMerge ? tryMergeSimpleBlock(I,
E, Limit) : 0;
561 if(NextLine.First->is(TT_FunctionLBrace) &&
562Style.BraceWrapping.AfterFunction) {
563 if(NextLine.Last->is(TT_LineComment))
567 if(Limit <= 2 || (Style.ColumnLimit == 0 && containsMustBreak(TheLine)))
571 unsignedMergedLines = 0;
572 if(MergeShortFunctions ||
574NextLine.First == NextLine.Last && I + 2 !=
E&&
575I[2]->First->is(tok::r_brace))) {
576MergedLines = tryMergeSimpleBlock(I + 1,
E, Limit);
584 autoIsElseLine = [&TheLine]() ->
bool{
585 constFormatToken *
First= TheLine->First;
586 if(
First->is(tok::kw_else))
589 return First->is(tok::r_brace) &&
First->Next &&
590 First->Next->is(tok::kw_else);
592 if(TheLine->First->is(tok::kw_if) ||
593(IsElseLine() && (Style.AllowShortIfStatementsOnASingleLine ==
595 returnStyle.AllowShortIfStatementsOnASingleLine
596? tryMergeSimpleControlStatement(I,
E, Limit)
599 if(TheLine->First->isOneOf(tok::kw_for, tok::kw_while, tok::kw_do,
601 returnStyle.AllowShortLoopsOnASingleLine
602? tryMergeSimpleControlStatement(I,
E, Limit)
605 if(TheLine->First->isOneOf(tok::kw_case, tok::kw_default)) {
606 returnStyle.AllowShortCaseLabelsOnASingleLine
607? tryMergeShortCaseLabels(I,
E, Limit)
610 if(TheLine->InPPDirective &&
611(TheLine->First->HasUnescapedNewline || TheLine->First->IsFirst)) {
612 returntryMergeSimplePPDirective(I,
E, Limit);
618tryMergeSimplePPDirective(ArrayRef<AnnotatedLine *>::const_iterator I,
619ArrayRef<AnnotatedLine *>::const_iterator
E,
623 if(I + 2 !=
E&& I[2]->InPPDirective && !I[2]->
First->HasUnescapedNewline)
625 if(1 + I[1]->
Last->TotalLength > Limit)
630 unsignedtryMergeNamespace(ArrayRef<AnnotatedLine *>::const_iterator I,
631ArrayRef<AnnotatedLine *>::const_iterator
E,
637 const auto&L1 = *I[1];
638 if(L1.InPPDirective != (*I)->InPPDirective ||
639(L1.InPPDirective && L1.First->HasUnescapedNewline)) {
643 if(std::distance(I,
E) <= 2)
647 const auto&L2 = *I[2];
651Limit = limitConsideringMacros(I + 1,
E, Limit);
653 if(!nextTwoLinesFitInto(I, Limit))
658 if(L1.First->is(tok::kw_namespace)) {
659 if(L1.Last->is(tok::comment) || !Style.CompactNamespaces)
662assert(Limit >= L1.Last->TotalLength + 3);
663 const autoInnerLimit = Limit - L1.Last->TotalLength - 3;
664 const autoMergedLines = tryMergeNamespace(I + 1,
E, InnerLimit);
665 if(MergedLines == 0)
667 const autoN = MergedLines + 2;
669 if(std::distance(I,
E) <= N)
673 if(I[N]->
First->is(tok::r_brace) && !I[N]->First->MustBreakBefore &&
674I[MergedLines + 1]->Last->isNot(tok::comment) &&
675nextNLinesFitInto(I, I + N + 1, Limit)) {
685 if(L1.Last->isNot(tok::semi))
689 if(L2.First->isNot(tok::r_brace) || L2.First->MustBreakBefore)
697tryMergeSimpleControlStatement(ArrayRef<AnnotatedLine *>::const_iterator I,
698ArrayRef<AnnotatedLine *>::const_iterator
E,
702 if(Style.BraceWrapping.AfterControlStatement ==
704I[1]->First->is(tok::l_brace) &&
708 if(I[1]->InPPDirective != (*I)->InPPDirective ||
709(I[1]->InPPDirective && I[1]->First->HasUnescapedNewline)) {
712Limit = limitConsideringMacros(I + 1,
E, Limit);
713AnnotatedLine &
Line= **I;
714 if(
Line.First->isNot(tok::kw_do) &&
Line.First->isNot(tok::kw_else) &&
715 Line.Last->isNot(tok::kw_else) &&
Line.Last->isNot(tok::r_paren)) {
719 if(
Line.First->is(tok::kw_do) &&
Line.Last->isNot(tok::kw_do))
721 if(1 + I[1]->
Last->TotalLength > Limit)
724 if(I[1]->
First->isOneOf(tok::semi, tok::kw_if, tok::kw_for, tok::kw_while,
725TT_ForEachMacro, TT_LineComment)) {
729 if(Style.AllowShortIfStatementsOnASingleLine ==
731 if(I + 2 !=
E&&
Line.startsWith(tok::kw_if) &&
732I[2]->First->is(tok::kw_else)) {
739 unsignedtryMergeShortCaseLabels(ArrayRef<AnnotatedLine *>::const_iterator I,
740ArrayRef<AnnotatedLine *>::const_iterator
E,
742 if(Limit == 0 || I + 1 ==
E||
743I[1]->
First->isOneOf(tok::kw_case, tok::kw_default)) {
746 if(I[0]->
Last->is(tok::l_brace) || I[1]->First->is(tok::l_brace))
748 unsignedNumStmts = 0;
750 boolEndsWithComment =
false;
751 boolInPPDirective = I[0]->InPPDirective;
752 boolInMacroBody = I[0]->InMacroBody;
753 const unsignedLevel = I[0]->Level;
754 for(; NumStmts < 3; ++NumStmts) {
755 if(I + 1 + NumStmts ==
E)
757 constAnnotatedLine *
Line= I[1 + NumStmts];
758 if(
Line->InPPDirective != InPPDirective)
760 if(
Line->InMacroBody != InMacroBody)
762 if(
Line->First->isOneOf(tok::kw_case, tok::kw_default, tok::r_brace))
764 if(
Line->First->isOneOf(tok::kw_if, tok::kw_for, tok::kw_switch,
769 if(
Line->First->is(tok::comment)) {
770 if(Level !=
Line->Level)
772 const auto*J = I + 2 + NumStmts;
773 for(; J !=
E; ++J) {
775 if(
Line->InPPDirective != InPPDirective)
777 if(
Line->First->isOneOf(tok::kw_case, tok::kw_default, tok::r_brace))
779 if(
Line->First->isNot(tok::comment) || Level !=
Line->Level)
784 if(
Line->Last->is(tok::comment))
785EndsWithComment =
true;
786Length += I[1 + NumStmts]->Last->TotalLength + 1;
788 if(NumStmts == 0 || NumStmts == 3 || Length > Limit)
793 unsignedtryMergeSimpleBlock(ArrayRef<AnnotatedLine *>::const_iterator I,
794ArrayRef<AnnotatedLine *>::const_iterator
E,
800AnnotatedLine &
Line= **I;
806 Line.First->isOneOf(tok::at, tok::minus, tok::plus)) {
812 if(
Line.First->is(tok::kw_case) ||
813(
Line.First->Next &&
Line.First->Next->is(tok::kw_else))) {
817 if(
Line.First->is(tok::kw_default)) {
818 constFormatToken *Tok =
Line.First->getNextNonComment();
819 if(Tok && Tok->is(tok::colon))
823 autoIsCtrlStmt = [](
const auto&
Line) {
824 return Line.First->isOneOf(tok::kw_if, tok::kw_else, tok::kw_while,
825tok::kw_do, tok::kw_for, TT_ForEachMacro);
828 const boolIsSplitBlock =
831I[1]->First->isNot(tok::r_brace));
833 if(IsCtrlStmt(
Line) ||
834 Line.First->isOneOf(tok::kw_try, tok::kw___try, tok::kw_catch,
835tok::kw___finally, tok::r_brace,
836Keywords.kw___except) ||
837 Line.startsWithExportBlock()) {
842 if(!Style.AllowShortIfStatementsOnASingleLine &&
843 Line.First->isOneOf(tok::kw_if, tok::kw_else) &&
844!Style.BraceWrapping.AfterControlStatement &&
845I[1]->First->isNot(tok::r_brace)) {
848 if(!Style.AllowShortIfStatementsOnASingleLine &&
849 Line.First->isOneOf(tok::kw_if, tok::kw_else) &&
850Style.BraceWrapping.AfterControlStatement ==
852I + 2 !=
E&& I[2]->First->isNot(tok::r_brace)) {
855 if(!Style.AllowShortLoopsOnASingleLine &&
856 Line.First->isOneOf(tok::kw_while, tok::kw_do, tok::kw_for,
858!Style.BraceWrapping.AfterControlStatement &&
859I[1]->First->isNot(tok::r_brace)) {
862 if(!Style.AllowShortLoopsOnASingleLine &&
863 Line.First->isOneOf(tok::kw_while, tok::kw_do, tok::kw_for,
865Style.BraceWrapping.AfterControlStatement ==
867I + 2 !=
E&& I[2]->First->isNot(tok::r_brace)) {
875 if(
Line.First->isOneOf(tok::kw_try, tok::kw___try, tok::kw_catch,
876Keywords.kw___except, tok::kw___finally)) {
881 if(
Line.endsWith(tok::l_brace)) {
883 Line.First->is(TT_BlockLBrace)) {
887 if(IsSplitBlock &&
Line.First ==
Line.Last &&
888I > AnnotatedLines.begin() &&
889(I[-1]->endsWith(tok::kw_else) || IsCtrlStmt(*I[-1]))) {
892FormatToken *Tok = I[1]->First;
893 autoShouldMerge = [Tok]() {
894 if(Tok->isNot(tok::r_brace) || Tok->MustBreakBefore)
896 constFormatToken *Next = Tok->getNextNonComment();
897 return!Next || Next->is(tok::semi);
902Tok->SpacesRequiredBefore =
903(Style.SpaceInEmptyBlock ||
Line.Last->is(tok::comment)) ? 1 : 0;
904Tok->CanBreakBefore =
true;
906}
else if(Limit != 0 && !
Line.startsWithNamespace() &&
907!startsExternCBlock(
Line)) {
909 if(isRecordLBrace(*
Line.Last))
915Limit = limitConsideringMacros(I + 2,
E, Limit);
917 if(!nextTwoLinesFitInto(I, Limit))
922 if(I[1]->
Last->is(TT_LineComment))
932 if(Tok->isNot(tok::r_brace))
936 if(Tok->Next && Tok->Next->is(tok::kw_else))
945 if(
Line.First ==
Line.Last &&
Line.First->isNot(TT_FunctionLBrace) &&
946Style.BraceWrapping.AfterControlStatement ==
953}
else if(I[1]->
First->is(tok::l_brace)) {
954 if(I[1]->
Last->is(TT_LineComment))
958 if(Limit <= 2 || (Style.ColumnLimit == 0 && containsMustBreak(*I)))
961 unsignedMergedLines = 0;
963(I[1]->First == I[1]->Last && I + 2 !=
E&&
964I[2]->First->is(tok::r_brace))) {
965MergedLines = tryMergeSimpleBlock(I + 1,
E, Limit);
978 unsignedlimitConsideringMacros(ArrayRef<AnnotatedLine *>::const_iterator I,
979ArrayRef<AnnotatedLine *>::const_iterator
E,
981 if(I[0]->InPPDirective && I + 1 !=
E&&
982!I[1]->
First->HasUnescapedNewline && I[1]->First->isNot(tok::eof)) {
983 returnLimit < 2 ? 0 : Limit - 2;
988 boolnextTwoLinesFitInto(ArrayRef<AnnotatedLine *>::const_iterator I,
990 if(I[1]->
First->MustBreakBefore || I[2]->First->MustBreakBefore)
992 return1 + I[1]->Last->TotalLength + 1 + I[2]->Last->TotalLength <= Limit;
995 boolnextNLinesFitInto(ArrayRef<AnnotatedLine *>::const_iterator I,
996ArrayRef<AnnotatedLine *>::const_iterator
E,
998 unsignedJoinedLength = 0;
999 for(
const auto*J = I + 1; J !=
E; ++J) {
1000 if((*J)->First->MustBreakBefore)
1003JoinedLength += 1 + (*J)->Last->TotalLength;
1004 if(JoinedLength > Limit)
1010 boolcontainsMustBreak(
constAnnotatedLine *
Line) {
1011assert(
Line->First);
1014 for(
constFormatToken *Tok =
Line->First->Next; Tok; Tok = Tok->Next)
1015 if(Tok->MustBreakBefore)
1020 voidjoin(AnnotatedLine &A,
constAnnotatedLine &B) {
1021assert(!A.Last->Next);
1022assert(!B.First->Previous);
1025A.Last->Next = B.First;
1026B.First->Previous = A.Last;
1027B.First->CanBreakBefore =
true;
1028 unsignedLengthA = A.Last->TotalLength + B.First->SpacesRequiredBefore;
1029 for(FormatToken *Tok = B.First; Tok; Tok = Tok->Next) {
1030Tok->TotalLength += LengthA;
1035 constFormatStyle &Style;
1036 constAdditionalKeywords &Keywords;
1037 constArrayRef<AnnotatedLine *>::const_iterator End;
1039ArrayRef<AnnotatedLine *>::const_iterator Next;
1040 constSmallVectorImpl<AnnotatedLine *> &AnnotatedLines;
1043static voidmarkFinalized(FormatToken *Tok) {
1044 if(Tok->is(tok::hash) && !Tok->Previous && Tok->Next &&
1045Tok->Next->isOneOf(tok::pp_if, tok::pp_ifdef, tok::pp_ifndef,
1046tok::pp_elif, tok::pp_elifdef, tok::pp_elifndef,
1047tok::pp_else, tok::pp_endif)) {
1050 for(; Tok; Tok = Tok->Next) {
1063Tok->SpacesRequiredBefore = 0;
1064 if(!Tok->MustBreakBeforeFinalized)
1065Tok->MustBreakBefore = 0;
1067Tok->Finalized =
true;
1073static voidprintLineState(
constLineState &State) {
1074llvm::dbgs() <<
"State: ";
1075 for(
constParenState &
P: State.Stack) {
1076llvm::dbgs() << (
P.Tok ?
P.Tok->TokenText :
"F") <<
"|"<<
P.Indent <<
"|" 1077<<
P.LastSpace <<
"|"<<
P.NestedBlockIndent <<
" ";
1079llvm::dbgs() << State.NextToken->TokenText <<
"\n";
1084classLineFormatter {
1086LineFormatter(ContinuationIndenter *
Indenter, WhitespaceManager *Whitespaces,
1087 constFormatStyle &Style,
1088UnwrappedLineFormatter *BlockFormatter)
1090BlockFormatter(BlockFormatter) {}
1091 virtual~LineFormatter() {}
1096 virtual unsignedformatLine(
constAnnotatedLine &
Line,
unsignedFirstIndent,
1097 unsignedFirstStartColumn,
boolDryRun) = 0;
1120 boolformatChildren(LineState &State,
bool NewLine,
boolDryRun,
1121 unsigned&Penalty) {
1122 constFormatToken *LBrace = State.NextToken->getPreviousNonComment();
1123 boolHasLBrace = LBrace && LBrace->is(tok::l_brace) && LBrace->is(
BK_Block);
1124FormatToken &
Previous= *State.NextToken->Previous;
1125 if(
Previous.Children.size() == 0 || (!HasLBrace && !LBrace->MacroParent)) {
1132 constParenState &
P= State.Stack.back();
1134 intAdditionalIndent =
1135 P.Indent -
Previous.Children[0]->Level * Style.IndentWidth;
1137BlockFormatter->format(
Previous.Children, DryRun, AdditionalIndent,
1142 if(
Previous.Children[0]->First->MustBreakBefore)
1150 if(
Previous.Children.size() > 1)
1153 constAnnotatedLine *Child =
Previous.Children[0];
1155 if(Child->Last->isTrailingComment())
1160 if(Style.ColumnLimit > 0 &&
1161Child->Last->TotalLength + State.Column + 2 > Style.ColumnLimit) {
1166Whitespaces->replaceWhitespace(
1167*Child->First,
0,
1,
1168State.Column,
false,
1169State.Line->InPPDirective);
1172formatLine(*Child, State.Column + 1,
0, DryRun);
1174markFinalized(Child->First);
1176State.Column += 1 + Child->Last->TotalLength;
1183WhitespaceManager *Whitespaces;
1184 constFormatStyle &Style;
1185UnwrappedLineFormatter *BlockFormatter;
1189classNoColumnLimitLineFormatter :
publicLineFormatter {
1191NoColumnLimitLineFormatter(ContinuationIndenter *
Indenter,
1192WhitespaceManager *Whitespaces,
1193 constFormatStyle &Style,
1194UnwrappedLineFormatter *BlockFormatter)
1195: LineFormatter(
Indenter, Whitespaces, Style, BlockFormatter) {}
1199 unsignedformatLine(
constAnnotatedLine &
Line,
unsignedFirstIndent,
1200 unsignedFirstStartColumn,
boolDryRun)
override{
1202LineState State =
Indenter->getInitialState(FirstIndent, FirstStartColumn,
1204 while(State.NextToken) {
1207(
Indenter->canBreak(State) && State.NextToken->NewlinesBefore > 0);
1208 unsignedPenalty = 0;
1209formatChildren(State, Newline,
false, Penalty);
1210 Indenter->addTokenToState(State, Newline,
false);
1217classNoLineBreakFormatter :
publicLineFormatter {
1219NoLineBreakFormatter(ContinuationIndenter *
Indenter,
1220WhitespaceManager *Whitespaces,
constFormatStyle &Style,
1221UnwrappedLineFormatter *BlockFormatter)
1222: LineFormatter(
Indenter, Whitespaces, Style, BlockFormatter) {}
1225 unsignedformatLine(
constAnnotatedLine &
Line,
unsignedFirstIndent,
1226 unsignedFirstStartColumn,
boolDryRun)
override{
1227 unsignedPenalty = 0;
1229 Indenter->getInitialState(FirstIndent, FirstStartColumn, &
Line, DryRun);
1230 while(State.NextToken) {
1231formatChildren(State,
false, DryRun, Penalty);
1233State,
State.NextToken->MustBreakBefore, DryRun);
1240classOptimizingLineFormatter :
publicLineFormatter {
1242OptimizingLineFormatter(ContinuationIndenter *
Indenter,
1243WhitespaceManager *Whitespaces,
1244 constFormatStyle &Style,
1245UnwrappedLineFormatter *BlockFormatter)
1246: LineFormatter(
Indenter, Whitespaces, Style, BlockFormatter) {}
1250 unsignedformatLine(
constAnnotatedLine &
Line,
unsignedFirstIndent,
1251 unsignedFirstStartColumn,
boolDryRun)
override{
1253 Indenter->getInitialState(FirstIndent, FirstStartColumn, &
Line, DryRun);
1258State.Stack.back().BreakBeforeParameter =
true;
1261 returnanalyzeSolutionSpace(State, DryRun);
1265 structCompareLineStatePointers {
1266 booloperator()(LineState *obj1, LineState *obj2)
const{
1267 return*obj1 < *obj2;
1276 typedefstd::pair<unsigned, unsigned> OrderedPenalty;
1281StateNode(
constLineState &State,
boolNewLine, StateNode *Previous)
1290 typedefstd::pair<OrderedPenalty, StateNode *> QueueItem;
1293 typedefstd::priority_queue<QueueItem, SmallVector<QueueItem>,
1294std::greater<QueueItem>>
1305 unsignedanalyzeSolutionSpace(LineState &InitialState,
boolDryRun) {
1306std::set<LineState *, CompareLineStatePointers> Seen;
1314StateNode *RootNode =
1315 new(Allocator.Allocate()) StateNode(InitialState,
false,
nullptr);
1316Queue.push(QueueItem(OrderedPenalty(0, Count), RootNode));
1319 unsignedPenalty = 0;
1322 while(!Queue.empty()) {
1324 if(Count > 25'000'000)
1327Penalty = Queue.top().first.first;
1328StateNode *
Node= Queue.top().second;
1329 if(!
Node->State.NextToken) {
1330LLVM_DEBUG(llvm::dbgs()
1331<<
"\n---\nPenalty for line: "<< Penalty <<
"\n");
1339 Node->State.IgnoreStackForComparison =
true;
1341 if(!Seen.insert(&
Node->State).second) {
1348addNextStateToQueue(Penalty,
Node,
false, &Count, &Queue);
1350addNextStateToQueue(Penalty,
Node,
true, &Count, &Queue);
1353 if(Queue.empty()) {
1356LLVM_DEBUG(llvm::dbgs() <<
"Could not find a solution.\n");
1362reconstructPath(InitialState, Queue.top().second);
1364LLVM_DEBUG(llvm::dbgs()
1365<<
"Total number of analyzed states: "<< Count <<
"\n");
1366LLVM_DEBUG(llvm::dbgs() <<
"---\n");
1375 voidaddNextStateToQueue(
unsignedPenalty, StateNode *PreviousNode,
1376 bool NewLine,
unsigned*Count, QueueType *Queue) {
1382StateNode *
Node=
new(Allocator.Allocate())
1383StateNode(PreviousNode->State,
NewLine, PreviousNode);
1384 if(!formatChildren(
Node->State,
NewLine,
true, Penalty))
1389Queue->push(QueueItem(OrderedPenalty(Penalty, *Count),
Node));
1395 voidreconstructPath(LineState &State, StateNode *Best) {
1398 while(Best->Previous) {
1399 Path.push_back(Best);
1400Best = Best->Previous;
1402 for(
const auto&
Node: llvm::reverse(
Path)) {
1403 unsignedPenalty = 0;
1404formatChildren(State,
Node->NewLine,
false, Penalty);
1405Penalty +=
Indenter->addTokenToState(State,
Node->NewLine,
false);
1408printLineState(
Node->Previous->State);
1409 if(
Node->NewLine) {
1410llvm::dbgs() <<
"Penalty for placing " 1411<<
Node->Previous->State.NextToken->Tok.getName()
1412<<
" on a new line: "<< Penalty <<
"\n";
1418llvm::SpecificBumpPtrAllocator<StateNode> Allocator;
1425 intAdditionalIndent,
boolFixBadIndentation,
unsignedFirstStartColumn,
1426 unsignedNextStartColumn,
unsignedLastStartColumn) {
1427LineJoiner Joiner(Style, Keywords, Lines);
1430std::pair<const SmallVectorImpl<AnnotatedLine *> *,
unsigned> CacheKey(
1431&Lines, AdditionalIndent);
1432 autoCacheIt = PenaltyCache.find(CacheKey);
1433 if(DryRun && CacheIt != PenaltyCache.end())
1434 returnCacheIt->second;
1436assert(!Lines.empty());
1437 unsignedPenalty = 0;
1438LevelIndentTracker IndentTracker(Style, Keywords, Lines[0]->Level,
1445 unsignedRangeMinLevel =
UINT_MAX;
1447 boolFirstLine =
true;
1449Joiner.getNextMergedLine(DryRun, IndentTracker);
1450 Line; PrevPrevLine = PreviousLine, PreviousLine =
Line,
Line= NextLine,
1451FirstLine =
false) {
1452assert(
Line->First);
1454 unsignedIndent = IndentTracker.getIndent();
1460 boolPreviousRBrace =
1461PreviousLine && PreviousLine->
startsWith(tok::r_brace);
1462 boolContinueFormatting =
1463TheLine.
Level> RangeMinLevel ||
1464(TheLine.
Level== RangeMinLevel && !PreviousRBrace &&
1467 boolFixIndentation = (FixBadIndentation || ContinueFormatting) &&
1469 boolShouldFormat = TheLine.
Affected|| FixIndentation;
1480 boolLastLine = TheLine.
First->
is(tok::eof);
1481formatFirstToken(TheLine, PreviousLine, PrevPrevLine, Lines, Indent,
1482LastLine ? LastStartColumn : NextStartColumn + Indent);
1485NextLine = Joiner.getNextMergedLine(DryRun, IndentTracker);
1486 unsignedColumnLimit = getColumnLimit(TheLine.
InPPDirective, NextLine);
1487 boolFitsIntoOneLine =
1491(!Style.isJavaScript() || !Style.JavaScriptWrapImports)) ||
1492(Style.isCSharp() &&
1494 if(Style.ColumnLimit == 0) {
1495NoColumnLimitLineFormatter(
Indenter, Whitespaces, Style,
this)
1496.formatLine(TheLine, NextStartColumn + Indent,
1497FirstLine ? FirstStartColumn : 0, DryRun);
1498}
else if(FitsIntoOneLine) {
1499Penalty += NoLineBreakFormatter(
Indenter, Whitespaces, Style,
this)
1500.formatLine(TheLine, NextStartColumn + Indent,
1501FirstLine ? FirstStartColumn : 0, DryRun);
1503Penalty += OptimizingLineFormatter(
Indenter, Whitespaces, Style,
this)
1504.formatLine(TheLine, NextStartColumn + Indent,
1505FirstLine ? FirstStartColumn : 0, DryRun);
1507RangeMinLevel = std::min(RangeMinLevel, TheLine.
Level);
1513 if(!Tok->Children.empty())
1514 format(Tok->Children, DryRun);
1519 boolStartsNewLine =
1522IndentTracker.adjustToUnmodifiedLine(TheLine);
1524 boolReformatLeadingWhitespace =
1525StartsNewLine && ((PreviousLine && PreviousLine->
Affected) ||
1528 if(ReformatLeadingWhitespace) {
1529formatFirstToken(TheLine, PreviousLine, PrevPrevLine, Lines,
1533Whitespaces->addUntouchableToken(*TheLine.
First,
1539Whitespaces->addUntouchableToken(*Tok, TheLine.
InPPDirective);
1541NextLine = Joiner.getNextMergedLine(DryRun, IndentTracker);
1545markFinalized(TheLine.
First);
1547PenaltyCache[CacheKey] = Penalty;
1556 const auto&RootToken = *
Line.First;
1560 if(RootToken.is(tok::r_brace) &&
1562(RootToken.Next->is(tok::semi) && !RootToken.Next->Next)) &&
1565Newlines = std::min(Newlines, 1u);
1568 if(!PreviousLine &&
Line.Level > 0)
1569Newlines = std::min(Newlines, 1u);
1570 if(Newlines == 0 && !RootToken.IsFirst)
1572 if(RootToken.IsFirst &&
1579PreviousLine->
Last->
is(tok::l_brace) &&
1583!startsExternCBlock(*PreviousLine)) {
1589 if(PreviousLine && PreviousLine->
endsWith(TT_NamespaceLBrace)) {
1592 else if(!
Line.startsWithNamespace())
1593Newlines = std::max(Newlines, 2u);
1596 if(
Line.startsWith(TT_NamespaceRBrace)) {
1599 else if(!PreviousLine->
startsWith(TT_NamespaceRBrace))
1600Newlines = std::max(Newlines, 2u);
1605 if(PreviousLine && RootToken.isAccessSpecifier()) {
1612Newlines = std::max(RootToken.NewlinesBefore, 1u);
1615 if(PreviousLine->
Last->
isOneOf(tok::semi, tok::r_brace) && Newlines <= 1)
1622 if(PreviousLine->
Last->
is(tok::comment))
1625previousToken = PreviousLine->
Last;
1626 if((!previousToken || previousToken->
isNot(tok::l_brace)) &&
1636(!PreviousLine->
InPPDirective|| !RootToken.HasUnescapedNewline)) {
1639 if(!RootToken.isAccessSpecifier()) {
1645Newlines = std::max(Newlines, 1u);
1648 if(RootToken.is(tok::r_brace))
1651Newlines = std::max(Newlines, 2u);
1660voidUnwrappedLineFormatter::formatFirstToken(
1661 constAnnotatedLine &
Line,
constAnnotatedLine *PreviousLine,
1662 constAnnotatedLine *PrevPrevLine,
1664 unsignedNewlineIndent) {
1665FormatToken &RootToken = *
Line.First;
1666 if(RootToken.is(tok::eof)) {
1667 unsignedNewlines = std::min(
1668RootToken.NewlinesBefore,
1669Style.KeepEmptyLines.AtEndOfFile ? Style.MaxEmptyLinesToKeep + 1 : 1);
1670 unsignedTokenIndent = Newlines ? NewlineIndent : 0;
1671Whitespaces->replaceWhitespace(RootToken, Newlines, TokenIndent,
1676 if(RootToken.Newlines < 0) {
1677RootToken.Newlines =
1679assert(RootToken.Newlines >= 0);
1682 if(RootToken.Newlines > 0)
1683Indent = NewlineIndent;
1687 if(!Style.isJavaScript() &&
1694Whitespaces->replaceWhitespace(RootToken, RootToken.Newlines, Indent, Indent,
1696 Line.InPPDirective &&
1697!RootToken.HasUnescapedNewline);
1701UnwrappedLineFormatter::getColumnLimit(
boolInPPDirective,
1702 constAnnotatedLine *NextLine)
const{
1705 boolContinuesPPDirective =
1710(NextLine->InPPDirective &&
1713!NextLine->First->HasUnescapedNewline));
1714 returnStyle.ColumnLimit - (ContinuesPPDirective ? 2 : 0);
This file contains the declaration of the FormatToken, a wrapper around Token with additional informa...
ContinuationIndenter * Indenter
Implements a combinatorial exploration of all the different linebreaks unwrapped lines can be formatt...
WhitespaceManager class manages whitespace around tokens and their replacements.
unsigned getSpellingLineNumber(SourceLocation Loc, bool *Invalid=nullptr) const
SourceLocation getLocation() const
Return a source location identifier for the specified offset in the current file.
bool LeadingEmptyLinesAffected
True if the leading empty lines of this line intersect with one of the input ranges.
bool Affected
True if this line should be formatted, i.e.
bool ContainsMacroCall
True if this line contains a macro call for which an expansion exists.
bool ChildrenAffected
True if one of this line's children intersects with an input range.
bool startsWithNamespace() const
true if this line starts a namespace definition.
bool endsWith(Ts... Tokens) const
true if this line ends with the given tokens in reversed order, ignoring comments.
bool startsWith(Ts... Tokens) const
true if this line starts with the given tokens in order, ignoring comments.
unsigned format(const SmallVectorImpl< AnnotatedLine * > &Lines, bool DryRun=false, int AdditionalIndent=0, bool FixBadIndentation=false, unsigned FirstStartColumn=0, unsigned NextStartColumn=0, unsigned LastStartColumn=0)
Format the current block and return the penalty.
@ MR_UnexpandedArg
The token is part of a macro argument that was previously formatted as expansion when formatting the ...
@ MR_ExpandedArg
The token was expanded from a macro argument when formatting the expanded token sequence.
const FormatToken * getNamespaceToken(const AnnotatedLine *Line, const SmallVectorImpl< AnnotatedLine * > &AnnotatedLines)
static auto computeNewlines(const AnnotatedLine &Line, const AnnotatedLine *PreviousLine, const AnnotatedLine *PrevPrevLine, const SmallVectorImpl< AnnotatedLine * > &Lines, const FormatStyle &Style)
StringRef getNamespaceTokenText(const AnnotatedLine *Line, const SmallVectorImpl< AnnotatedLine * > &AnnotatedLines)
@ LT_CommentAbovePPDirective
@ LT_PreprocessorDirective
ObjCKeywordKind
Provides a namespace for Objective-C keywords which start with an '@'.
The JSON file list parser is used to communicate input to InstallAPI.
if(T->getSizeExpr()) TRY_TO(TraverseStmt(const_cast< Expr * >(T -> getSizeExpr())))
bool AtStartOfFile
Keep empty lines at start of file.
bool AtStartOfBlock
Keep empty lines at start of a block.
The FormatStyle is used to configure the formatting to follow specific guidelines.
@ LK_Java
Should be used for Java.
@ ELBAMS_LogicalBlock
Add empty line only when access modifier starts a new logical block.
@ ELBAMS_Never
Remove all empty lines before access modifiers.
@ ELBAMS_Always
Always add empty line before access modifiers unless access modifier is at the start of struct or cla...
@ ELBAMS_Leave
Keep existing empty lines before access modifiers.
WrapNamespaceBodyWithEmptyLinesStyle WrapNamespaceBodyWithEmptyLines
Wrap namespace body with empty lines.
@ PPDIS_BeforeHash
Indents directives before the hash.
@ PPDIS_None
Does not indent any directives.
@ SBS_Empty
Only merge empty blocks.
@ SBS_Never
Never merge blocks into a single line.
@ SIS_WithoutElse
Put short ifs on the same line only if there is no else statement.
@ SIS_AllIfsAndElse
Always put short ifs, else ifs and else statements on the same line.
@ BWACS_Always
Always wrap braces after a control statement.
@ BWACS_MultiLine
Only wrap braces after a multi-line control statement.
@ WNBWELS_Leave
Keep existing newlines at the beginning and the end of namespace body.
@ WNBWELS_Never
Remove all empty lines at the beginning and the end of namespace body.
@ SFS_All
Merge all functions fitting on a single line.
@ SFS_Empty
Only merge empty functions.
@ SFS_InlineOnly
Only merge functions defined inside a class.
KeepEmptyLinesStyle KeepEmptyLines
Which empty lines are kept.
unsigned MaxEmptyLinesToKeep
The maximum number of consecutive empty lines to keep.
@ ELAAMS_Always
Always add empty line after access modifiers if there are none.
@ ELAAMS_Never
Remove all empty lines after access modifiers.
@ ELAAMS_Leave
Keep existing empty lines after access modifiers.
EmptyLineBeforeAccessModifierStyle EmptyLineBeforeAccessModifier
Defines in which cases to put empty line before access modifiers.
EmptyLineAfterAccessModifierStyle EmptyLineAfterAccessModifier
Defines when to put an empty line after access modifiers.
A wrapper around a Token storing information about the whitespace characters preceding it.
unsigned OriginalColumn
The original 0-based column of this token, including expanded tabs.
FormatToken * getPreviousNonComment() const
Returns the previous token ignoring comments.
FormatToken * Next
The next token in the unwrapped line.
unsigned NewlinesBefore
The number of newlines immediately before the Token.
bool is(tok::TokenKind Kind) const
unsigned TotalLength
The total length of the unwrapped line up to and including this token.
bool isOneOf(A K1, B K2) const
unsigned IsFirst
Indicates that this is the first token of the file.
bool isAccessSpecifier(bool ColonRequired=true) const
bool FormatComplete
A value of false means that any of the affected ranges were not formatted due to a non-recoverable sy...
unsigned Line
If FormatComplete is false, Line records a one-based original line number at which a syntax error mig...
static const size_t kInvalidIndex
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4