1 1.1 joerg //===--- RawCommentList.cpp - Processing raw comments -----------*- C++ -*-===// 2 1.1 joerg // 3 1.1 joerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 1.1 joerg // See https://llvm.org/LICENSE.txt for license information. 5 1.1 joerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 1.1 joerg // 7 1.1 joerg //===----------------------------------------------------------------------===// 8 1.1 joerg 9 1.1 joerg #include "clang/AST/RawCommentList.h" 10 1.1 joerg #include "clang/AST/ASTContext.h" 11 1.1 joerg #include "clang/AST/Comment.h" 12 1.1 joerg #include "clang/AST/CommentBriefParser.h" 13 1.1 joerg #include "clang/AST/CommentCommandTraits.h" 14 1.1 joerg #include "clang/AST/CommentLexer.h" 15 1.1 joerg #include "clang/AST/CommentParser.h" 16 1.1 joerg #include "clang/AST/CommentSema.h" 17 1.1 joerg #include "clang/Basic/CharInfo.h" 18 1.1 joerg #include "llvm/ADT/STLExtras.h" 19 1.1.1.2 joerg #include "llvm/Support/Allocator.h" 20 1.1 joerg 21 1.1 joerg using namespace clang; 22 1.1 joerg 23 1.1 joerg namespace { 24 1.1 joerg /// Get comment kind and bool describing if it is a trailing comment. 25 1.1 joerg std::pair<RawComment::CommentKind, bool> getCommentKind(StringRef Comment, 26 1.1 joerg bool ParseAllComments) { 27 1.1 joerg const size_t MinCommentLength = ParseAllComments ? 2 : 3; 28 1.1 joerg if ((Comment.size() < MinCommentLength) || Comment[0] != '/') 29 1.1 joerg return std::make_pair(RawComment::RCK_Invalid, false); 30 1.1 joerg 31 1.1 joerg RawComment::CommentKind K; 32 1.1 joerg if (Comment[1] == '/') { 33 1.1 joerg if (Comment.size() < 3) 34 1.1 joerg return std::make_pair(RawComment::RCK_OrdinaryBCPL, false); 35 1.1 joerg 36 1.1 joerg if (Comment[2] == '/') 37 1.1 joerg K = RawComment::RCK_BCPLSlash; 38 1.1 joerg else if (Comment[2] == '!') 39 1.1 joerg K = RawComment::RCK_BCPLExcl; 40 1.1 joerg else 41 1.1 joerg return std::make_pair(RawComment::RCK_OrdinaryBCPL, false); 42 1.1 joerg } else { 43 1.1 joerg assert(Comment.size() >= 4); 44 1.1 joerg 45 1.1 joerg // Comment lexer does not understand escapes in comment markers, so pretend 46 1.1 joerg // that this is not a comment. 47 1.1 joerg if (Comment[1] != '*' || 48 1.1 joerg Comment[Comment.size() - 2] != '*' || 49 1.1 joerg Comment[Comment.size() - 1] != '/') 50 1.1 joerg return std::make_pair(RawComment::RCK_Invalid, false); 51 1.1 joerg 52 1.1 joerg if (Comment[2] == '*') 53 1.1 joerg K = RawComment::RCK_JavaDoc; 54 1.1 joerg else if (Comment[2] == '!') 55 1.1 joerg K = RawComment::RCK_Qt; 56 1.1 joerg else 57 1.1 joerg return std::make_pair(RawComment::RCK_OrdinaryC, false); 58 1.1 joerg } 59 1.1 joerg const bool TrailingComment = (Comment.size() > 3) && (Comment[3] == '<'); 60 1.1 joerg return std::make_pair(K, TrailingComment); 61 1.1 joerg } 62 1.1 joerg 63 1.1 joerg bool mergedCommentIsTrailingComment(StringRef Comment) { 64 1.1 joerg return (Comment.size() > 3) && (Comment[3] == '<'); 65 1.1 joerg } 66 1.1 joerg 67 1.1 joerg /// Returns true if R1 and R2 both have valid locations that start on the same 68 1.1 joerg /// column. 69 1.1 joerg bool commentsStartOnSameColumn(const SourceManager &SM, const RawComment &R1, 70 1.1 joerg const RawComment &R2) { 71 1.1 joerg SourceLocation L1 = R1.getBeginLoc(); 72 1.1 joerg SourceLocation L2 = R2.getBeginLoc(); 73 1.1 joerg bool Invalid = false; 74 1.1 joerg unsigned C1 = SM.getPresumedColumnNumber(L1, &Invalid); 75 1.1 joerg if (!Invalid) { 76 1.1 joerg unsigned C2 = SM.getPresumedColumnNumber(L2, &Invalid); 77 1.1 joerg return !Invalid && (C1 == C2); 78 1.1 joerg } 79 1.1 joerg return false; 80 1.1 joerg } 81 1.1 joerg } // unnamed namespace 82 1.1 joerg 83 1.1 joerg /// Determines whether there is only whitespace in `Buffer` between `P` 84 1.1 joerg /// and the previous line. 85 1.1 joerg /// \param Buffer The buffer to search in. 86 1.1 joerg /// \param P The offset from the beginning of `Buffer` to start from. 87 1.1 joerg /// \return true if all of the characters in `Buffer` ranging from the closest 88 1.1 joerg /// line-ending character before `P` (or the beginning of `Buffer`) to `P - 1` 89 1.1 joerg /// are whitespace. 90 1.1 joerg static bool onlyWhitespaceOnLineBefore(const char *Buffer, unsigned P) { 91 1.1 joerg // Search backwards until we see linefeed or carriage return. 92 1.1 joerg for (unsigned I = P; I != 0; --I) { 93 1.1 joerg char C = Buffer[I - 1]; 94 1.1 joerg if (isVerticalWhitespace(C)) 95 1.1 joerg return true; 96 1.1 joerg if (!isHorizontalWhitespace(C)) 97 1.1 joerg return false; 98 1.1 joerg } 99 1.1 joerg // We hit the beginning of the buffer. 100 1.1 joerg return true; 101 1.1 joerg } 102 1.1 joerg 103 1.1 joerg /// Returns whether `K` is an ordinary comment kind. 104 1.1 joerg static bool isOrdinaryKind(RawComment::CommentKind K) { 105 1.1 joerg return (K == RawComment::RCK_OrdinaryBCPL) || 106 1.1 joerg (K == RawComment::RCK_OrdinaryC); 107 1.1 joerg } 108 1.1 joerg 109 1.1 joerg RawComment::RawComment(const SourceManager &SourceMgr, SourceRange SR, 110 1.1 joerg const CommentOptions &CommentOpts, bool Merged) : 111 1.1 joerg Range(SR), RawTextValid(false), BriefTextValid(false), 112 1.1 joerg IsAttached(false), IsTrailingComment(false), 113 1.1 joerg IsAlmostTrailingComment(false) { 114 1.1 joerg // Extract raw comment text, if possible. 115 1.1 joerg if (SR.getBegin() == SR.getEnd() || getRawText(SourceMgr).empty()) { 116 1.1 joerg Kind = RCK_Invalid; 117 1.1 joerg return; 118 1.1 joerg } 119 1.1 joerg 120 1.1 joerg // Guess comment kind. 121 1.1 joerg std::pair<CommentKind, bool> K = 122 1.1 joerg getCommentKind(RawText, CommentOpts.ParseAllComments); 123 1.1 joerg 124 1.1 joerg // Guess whether an ordinary comment is trailing. 125 1.1 joerg if (CommentOpts.ParseAllComments && isOrdinaryKind(K.first)) { 126 1.1 joerg FileID BeginFileID; 127 1.1 joerg unsigned BeginOffset; 128 1.1 joerg std::tie(BeginFileID, BeginOffset) = 129 1.1 joerg SourceMgr.getDecomposedLoc(Range.getBegin()); 130 1.1 joerg if (BeginOffset != 0) { 131 1.1 joerg bool Invalid = false; 132 1.1 joerg const char *Buffer = 133 1.1 joerg SourceMgr.getBufferData(BeginFileID, &Invalid).data(); 134 1.1 joerg IsTrailingComment |= 135 1.1 joerg (!Invalid && !onlyWhitespaceOnLineBefore(Buffer, BeginOffset)); 136 1.1 joerg } 137 1.1 joerg } 138 1.1 joerg 139 1.1 joerg if (!Merged) { 140 1.1 joerg Kind = K.first; 141 1.1 joerg IsTrailingComment |= K.second; 142 1.1 joerg 143 1.1 joerg IsAlmostTrailingComment = RawText.startswith("//<") || 144 1.1 joerg RawText.startswith("/*<"); 145 1.1 joerg } else { 146 1.1 joerg Kind = RCK_Merged; 147 1.1 joerg IsTrailingComment = 148 1.1 joerg IsTrailingComment || mergedCommentIsTrailingComment(RawText); 149 1.1 joerg } 150 1.1 joerg } 151 1.1 joerg 152 1.1 joerg StringRef RawComment::getRawTextSlow(const SourceManager &SourceMgr) const { 153 1.1 joerg FileID BeginFileID; 154 1.1 joerg FileID EndFileID; 155 1.1 joerg unsigned BeginOffset; 156 1.1 joerg unsigned EndOffset; 157 1.1 joerg 158 1.1 joerg std::tie(BeginFileID, BeginOffset) = 159 1.1 joerg SourceMgr.getDecomposedLoc(Range.getBegin()); 160 1.1 joerg std::tie(EndFileID, EndOffset) = SourceMgr.getDecomposedLoc(Range.getEnd()); 161 1.1 joerg 162 1.1 joerg const unsigned Length = EndOffset - BeginOffset; 163 1.1 joerg if (Length < 2) 164 1.1 joerg return StringRef(); 165 1.1 joerg 166 1.1 joerg // The comment can't begin in one file and end in another. 167 1.1 joerg assert(BeginFileID == EndFileID); 168 1.1 joerg 169 1.1 joerg bool Invalid = false; 170 1.1 joerg const char *BufferStart = SourceMgr.getBufferData(BeginFileID, 171 1.1 joerg &Invalid).data(); 172 1.1 joerg if (Invalid) 173 1.1 joerg return StringRef(); 174 1.1 joerg 175 1.1 joerg return StringRef(BufferStart + BeginOffset, Length); 176 1.1 joerg } 177 1.1 joerg 178 1.1 joerg const char *RawComment::extractBriefText(const ASTContext &Context) const { 179 1.1 joerg // Lazily initialize RawText using the accessor before using it. 180 1.1 joerg (void)getRawText(Context.getSourceManager()); 181 1.1 joerg 182 1.1 joerg // Since we will be copying the resulting text, all allocations made during 183 1.1 joerg // parsing are garbage after resulting string is formed. Thus we can use 184 1.1 joerg // a separate allocator for all temporary stuff. 185 1.1 joerg llvm::BumpPtrAllocator Allocator; 186 1.1 joerg 187 1.1 joerg comments::Lexer L(Allocator, Context.getDiagnostics(), 188 1.1 joerg Context.getCommentCommandTraits(), 189 1.1 joerg Range.getBegin(), 190 1.1 joerg RawText.begin(), RawText.end()); 191 1.1 joerg comments::BriefParser P(L, Context.getCommentCommandTraits()); 192 1.1 joerg 193 1.1 joerg const std::string Result = P.Parse(); 194 1.1 joerg const unsigned BriefTextLength = Result.size(); 195 1.1 joerg char *BriefTextPtr = new (Context) char[BriefTextLength + 1]; 196 1.1 joerg memcpy(BriefTextPtr, Result.c_str(), BriefTextLength + 1); 197 1.1 joerg BriefText = BriefTextPtr; 198 1.1 joerg BriefTextValid = true; 199 1.1 joerg 200 1.1 joerg return BriefTextPtr; 201 1.1 joerg } 202 1.1 joerg 203 1.1 joerg comments::FullComment *RawComment::parse(const ASTContext &Context, 204 1.1 joerg const Preprocessor *PP, 205 1.1 joerg const Decl *D) const { 206 1.1 joerg // Lazily initialize RawText using the accessor before using it. 207 1.1 joerg (void)getRawText(Context.getSourceManager()); 208 1.1 joerg 209 1.1 joerg comments::Lexer L(Context.getAllocator(), Context.getDiagnostics(), 210 1.1 joerg Context.getCommentCommandTraits(), 211 1.1 joerg getSourceRange().getBegin(), 212 1.1 joerg RawText.begin(), RawText.end()); 213 1.1 joerg comments::Sema S(Context.getAllocator(), Context.getSourceManager(), 214 1.1 joerg Context.getDiagnostics(), 215 1.1 joerg Context.getCommentCommandTraits(), 216 1.1 joerg PP); 217 1.1 joerg S.setDecl(D); 218 1.1 joerg comments::Parser P(L, S, Context.getAllocator(), Context.getSourceManager(), 219 1.1 joerg Context.getDiagnostics(), 220 1.1 joerg Context.getCommentCommandTraits()); 221 1.1 joerg 222 1.1 joerg return P.parseFullComment(); 223 1.1 joerg } 224 1.1 joerg 225 1.1 joerg static bool onlyWhitespaceBetween(SourceManager &SM, 226 1.1 joerg SourceLocation Loc1, SourceLocation Loc2, 227 1.1 joerg unsigned MaxNewlinesAllowed) { 228 1.1 joerg std::pair<FileID, unsigned> Loc1Info = SM.getDecomposedLoc(Loc1); 229 1.1 joerg std::pair<FileID, unsigned> Loc2Info = SM.getDecomposedLoc(Loc2); 230 1.1 joerg 231 1.1 joerg // Question does not make sense if locations are in different files. 232 1.1 joerg if (Loc1Info.first != Loc2Info.first) 233 1.1 joerg return false; 234 1.1 joerg 235 1.1 joerg bool Invalid = false; 236 1.1 joerg const char *Buffer = SM.getBufferData(Loc1Info.first, &Invalid).data(); 237 1.1 joerg if (Invalid) 238 1.1 joerg return false; 239 1.1 joerg 240 1.1 joerg unsigned NumNewlines = 0; 241 1.1 joerg assert(Loc1Info.second <= Loc2Info.second && "Loc1 after Loc2!"); 242 1.1 joerg // Look for non-whitespace characters and remember any newlines seen. 243 1.1 joerg for (unsigned I = Loc1Info.second; I != Loc2Info.second; ++I) { 244 1.1 joerg switch (Buffer[I]) { 245 1.1 joerg default: 246 1.1 joerg return false; 247 1.1 joerg case ' ': 248 1.1 joerg case '\t': 249 1.1 joerg case '\f': 250 1.1 joerg case '\v': 251 1.1 joerg break; 252 1.1 joerg case '\r': 253 1.1 joerg case '\n': 254 1.1 joerg ++NumNewlines; 255 1.1 joerg 256 1.1 joerg // Check if we have found more than the maximum allowed number of 257 1.1 joerg // newlines. 258 1.1 joerg if (NumNewlines > MaxNewlinesAllowed) 259 1.1 joerg return false; 260 1.1 joerg 261 1.1 joerg // Collapse \r\n and \n\r into a single newline. 262 1.1 joerg if (I + 1 != Loc2Info.second && 263 1.1 joerg (Buffer[I + 1] == '\n' || Buffer[I + 1] == '\r') && 264 1.1 joerg Buffer[I] != Buffer[I + 1]) 265 1.1 joerg ++I; 266 1.1 joerg break; 267 1.1 joerg } 268 1.1 joerg } 269 1.1 joerg 270 1.1 joerg return true; 271 1.1 joerg } 272 1.1 joerg 273 1.1 joerg void RawCommentList::addComment(const RawComment &RC, 274 1.1 joerg const CommentOptions &CommentOpts, 275 1.1 joerg llvm::BumpPtrAllocator &Allocator) { 276 1.1 joerg if (RC.isInvalid()) 277 1.1 joerg return; 278 1.1 joerg 279 1.1 joerg // Ordinary comments are not interesting for us. 280 1.1 joerg if (RC.isOrdinary() && !CommentOpts.ParseAllComments) 281 1.1 joerg return; 282 1.1 joerg 283 1.1 joerg std::pair<FileID, unsigned> Loc = 284 1.1 joerg SourceMgr.getDecomposedLoc(RC.getBeginLoc()); 285 1.1 joerg 286 1.1 joerg const FileID CommentFile = Loc.first; 287 1.1 joerg const unsigned CommentOffset = Loc.second; 288 1.1 joerg 289 1.1 joerg // If this is the first Doxygen comment, save it (because there isn't 290 1.1 joerg // anything to merge it with). 291 1.1 joerg if (OrderedComments[CommentFile].empty()) { 292 1.1 joerg OrderedComments[CommentFile][CommentOffset] = 293 1.1 joerg new (Allocator) RawComment(RC); 294 1.1 joerg return; 295 1.1 joerg } 296 1.1 joerg 297 1.1 joerg const RawComment &C1 = *OrderedComments[CommentFile].rbegin()->second; 298 1.1 joerg const RawComment &C2 = RC; 299 1.1 joerg 300 1.1 joerg // Merge comments only if there is only whitespace between them. 301 1.1 joerg // Can't merge trailing and non-trailing comments unless the second is 302 1.1 joerg // non-trailing ordinary in the same column, as in the case: 303 1.1 joerg // int x; // documents x 304 1.1 joerg // // more text 305 1.1 joerg // versus: 306 1.1 joerg // int x; // documents x 307 1.1 joerg // int y; // documents y 308 1.1 joerg // or: 309 1.1 joerg // int x; // documents x 310 1.1 joerg // // documents y 311 1.1 joerg // int y; 312 1.1 joerg // Merge comments if they are on same or consecutive lines. 313 1.1 joerg if ((C1.isTrailingComment() == C2.isTrailingComment() || 314 1.1 joerg (C1.isTrailingComment() && !C2.isTrailingComment() && 315 1.1 joerg isOrdinaryKind(C2.getKind()) && 316 1.1 joerg commentsStartOnSameColumn(SourceMgr, C1, C2))) && 317 1.1 joerg onlyWhitespaceBetween(SourceMgr, C1.getEndLoc(), C2.getBeginLoc(), 318 1.1 joerg /*MaxNewlinesAllowed=*/1)) { 319 1.1 joerg SourceRange MergedRange(C1.getBeginLoc(), C2.getEndLoc()); 320 1.1 joerg *OrderedComments[CommentFile].rbegin()->second = 321 1.1 joerg RawComment(SourceMgr, MergedRange, CommentOpts, true); 322 1.1 joerg } else { 323 1.1 joerg OrderedComments[CommentFile][CommentOffset] = 324 1.1 joerg new (Allocator) RawComment(RC); 325 1.1 joerg } 326 1.1 joerg } 327 1.1 joerg 328 1.1 joerg const std::map<unsigned, RawComment *> * 329 1.1 joerg RawCommentList::getCommentsInFile(FileID File) const { 330 1.1 joerg auto CommentsInFile = OrderedComments.find(File); 331 1.1 joerg if (CommentsInFile == OrderedComments.end()) 332 1.1 joerg return nullptr; 333 1.1 joerg 334 1.1 joerg return &CommentsInFile->second; 335 1.1 joerg } 336 1.1 joerg 337 1.1 joerg bool RawCommentList::empty() const { return OrderedComments.empty(); } 338 1.1 joerg 339 1.1 joerg unsigned RawCommentList::getCommentBeginLine(RawComment *C, FileID File, 340 1.1 joerg unsigned Offset) const { 341 1.1 joerg auto Cached = CommentBeginLine.find(C); 342 1.1 joerg if (Cached != CommentBeginLine.end()) 343 1.1 joerg return Cached->second; 344 1.1 joerg const unsigned Line = SourceMgr.getLineNumber(File, Offset); 345 1.1 joerg CommentBeginLine[C] = Line; 346 1.1 joerg return Line; 347 1.1 joerg } 348 1.1 joerg 349 1.1 joerg unsigned RawCommentList::getCommentEndOffset(RawComment *C) const { 350 1.1 joerg auto Cached = CommentEndOffset.find(C); 351 1.1 joerg if (Cached != CommentEndOffset.end()) 352 1.1 joerg return Cached->second; 353 1.1 joerg const unsigned Offset = 354 1.1 joerg SourceMgr.getDecomposedLoc(C->getSourceRange().getEnd()).second; 355 1.1 joerg CommentEndOffset[C] = Offset; 356 1.1 joerg return Offset; 357 1.1 joerg } 358 1.1 joerg 359 1.1 joerg std::string RawComment::getFormattedText(const SourceManager &SourceMgr, 360 1.1 joerg DiagnosticsEngine &Diags) const { 361 1.1 joerg llvm::StringRef CommentText = getRawText(SourceMgr); 362 1.1 joerg if (CommentText.empty()) 363 1.1 joerg return ""; 364 1.1 joerg 365 1.1 joerg llvm::BumpPtrAllocator Allocator; 366 1.1 joerg // We do not parse any commands, so CommentOptions are ignored by 367 1.1 joerg // comments::Lexer. Therefore, we just use default-constructed options. 368 1.1 joerg CommentOptions DefOpts; 369 1.1 joerg comments::CommandTraits EmptyTraits(Allocator, DefOpts); 370 1.1 joerg comments::Lexer L(Allocator, Diags, EmptyTraits, getSourceRange().getBegin(), 371 1.1 joerg CommentText.begin(), CommentText.end(), 372 1.1 joerg /*ParseCommands=*/false); 373 1.1 joerg 374 1.1 joerg std::string Result; 375 1.1 joerg // A column number of the first non-whitespace token in the comment text. 376 1.1 joerg // We skip whitespace up to this column, but keep the whitespace after this 377 1.1 joerg // column. IndentColumn is calculated when lexing the first line and reused 378 1.1 joerg // for the rest of lines. 379 1.1 joerg unsigned IndentColumn = 0; 380 1.1 joerg 381 1.1 joerg // Processes one line of the comment and adds it to the result. 382 1.1 joerg // Handles skipping the indent at the start of the line. 383 1.1 joerg // Returns false when eof is reached and true otherwise. 384 1.1 joerg auto LexLine = [&](bool IsFirstLine) -> bool { 385 1.1 joerg comments::Token Tok; 386 1.1 joerg // Lex the first token on the line. We handle it separately, because we to 387 1.1 joerg // fix up its indentation. 388 1.1 joerg L.lex(Tok); 389 1.1 joerg if (Tok.is(comments::tok::eof)) 390 1.1 joerg return false; 391 1.1 joerg if (Tok.is(comments::tok::newline)) { 392 1.1 joerg Result += "\n"; 393 1.1 joerg return true; 394 1.1 joerg } 395 1.1 joerg llvm::StringRef TokText = L.getSpelling(Tok, SourceMgr); 396 1.1 joerg bool LocInvalid = false; 397 1.1 joerg unsigned TokColumn = 398 1.1 joerg SourceMgr.getSpellingColumnNumber(Tok.getLocation(), &LocInvalid); 399 1.1 joerg assert(!LocInvalid && "getFormattedText for invalid location"); 400 1.1 joerg 401 1.1 joerg // Amount of leading whitespace in TokText. 402 1.1 joerg size_t WhitespaceLen = TokText.find_first_not_of(" \t"); 403 1.1 joerg if (WhitespaceLen == StringRef::npos) 404 1.1 joerg WhitespaceLen = TokText.size(); 405 1.1 joerg // Remember the amount of whitespace we skipped in the first line to remove 406 1.1 joerg // indent up to that column in the following lines. 407 1.1 joerg if (IsFirstLine) 408 1.1 joerg IndentColumn = TokColumn + WhitespaceLen; 409 1.1 joerg 410 1.1 joerg // Amount of leading whitespace we actually want to skip. 411 1.1 joerg // For the first line we skip all the whitespace. 412 1.1 joerg // For the rest of the lines, we skip whitespace up to IndentColumn. 413 1.1 joerg unsigned SkipLen = 414 1.1 joerg IsFirstLine 415 1.1 joerg ? WhitespaceLen 416 1.1 joerg : std::min<size_t>( 417 1.1 joerg WhitespaceLen, 418 1.1 joerg std::max<int>(static_cast<int>(IndentColumn) - TokColumn, 0)); 419 1.1 joerg llvm::StringRef Trimmed = TokText.drop_front(SkipLen); 420 1.1 joerg Result += Trimmed; 421 1.1 joerg // Lex all tokens in the rest of the line. 422 1.1 joerg for (L.lex(Tok); Tok.isNot(comments::tok::eof); L.lex(Tok)) { 423 1.1 joerg if (Tok.is(comments::tok::newline)) { 424 1.1 joerg Result += "\n"; 425 1.1 joerg return true; 426 1.1 joerg } 427 1.1 joerg Result += L.getSpelling(Tok, SourceMgr); 428 1.1 joerg } 429 1.1 joerg // We've reached the end of file token. 430 1.1 joerg return false; 431 1.1 joerg }; 432 1.1 joerg 433 1.1 joerg auto DropTrailingNewLines = [](std::string &Str) { 434 1.1.1.2 joerg while (!Str.empty() && Str.back() == '\n') 435 1.1 joerg Str.pop_back(); 436 1.1 joerg }; 437 1.1 joerg 438 1.1 joerg // Process first line separately to remember indent for the following lines. 439 1.1 joerg if (!LexLine(/*IsFirstLine=*/true)) { 440 1.1 joerg DropTrailingNewLines(Result); 441 1.1 joerg return Result; 442 1.1 joerg } 443 1.1 joerg // Process the rest of the lines. 444 1.1 joerg while (LexLine(/*IsFirstLine=*/false)) 445 1.1 joerg ; 446 1.1 joerg DropTrailingNewLines(Result); 447 1.1 joerg return Result; 448 1.1 joerg } 449