Home | History | Annotate | Download | only in AST
      1 //===--- CommentParser.cpp - Doxygen comment parser -----------------------===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 
     10 #include "clang/AST/CommentParser.h"
     11 #include "clang/AST/CommentCommandTraits.h"
     12 #include "clang/AST/CommentDiagnostic.h"
     13 #include "clang/AST/CommentSema.h"
     14 #include "clang/Basic/CharInfo.h"
     15 #include "clang/Basic/SourceManager.h"
     16 #include "llvm/Support/ErrorHandling.h"
     17 
     18 namespace clang {
     19 
     20 static inline bool isWhitespace(llvm::StringRef S) {
     21   for (StringRef::const_iterator I = S.begin(), E = S.end(); I != E; ++I) {
     22     if (!isWhitespace(*I))
     23       return false;
     24   }
     25   return true;
     26 }
     27 
     28 namespace comments {
     29 
     30 /// Re-lexes a sequence of tok::text tokens.
     31 class TextTokenRetokenizer {
     32   llvm::BumpPtrAllocator &Allocator;
     33   Parser &P;
     34 
     35   /// This flag is set when there are no more tokens we can fetch from lexer.
     36   bool NoMoreInterestingTokens;
     37 
     38   /// Token buffer: tokens we have processed and lookahead.
     39   SmallVector<Token, 16> Toks;
     40 
     41   /// A position in \c Toks.
     42   struct Position {
     43     unsigned CurToken;
     44     const char *BufferStart;
     45     const char *BufferEnd;
     46     const char *BufferPtr;
     47     SourceLocation BufferStartLoc;
     48   };
     49 
     50   /// Current position in Toks.
     51   Position Pos;
     52 
     53   bool isEnd() const {
     54     return Pos.CurToken >= Toks.size();
     55   }
     56 
     57   /// Sets up the buffer pointers to point to current token.
     58   void setupBuffer() {
     59     assert(!isEnd());
     60     const Token &Tok = Toks[Pos.CurToken];
     61 
     62     Pos.BufferStart = Tok.getText().begin();
     63     Pos.BufferEnd = Tok.getText().end();
     64     Pos.BufferPtr = Pos.BufferStart;
     65     Pos.BufferStartLoc = Tok.getLocation();
     66   }
     67 
     68   SourceLocation getSourceLocation() const {
     69     const unsigned CharNo = Pos.BufferPtr - Pos.BufferStart;
     70     return Pos.BufferStartLoc.getLocWithOffset(CharNo);
     71   }
     72 
     73   char peek() const {
     74     assert(!isEnd());
     75     assert(Pos.BufferPtr != Pos.BufferEnd);
     76     return *Pos.BufferPtr;
     77   }
     78 
     79   void consumeChar() {
     80     assert(!isEnd());
     81     assert(Pos.BufferPtr != Pos.BufferEnd);
     82     Pos.BufferPtr++;
     83     if (Pos.BufferPtr == Pos.BufferEnd) {
     84       Pos.CurToken++;
     85       if (isEnd() && !addToken())
     86         return;
     87 
     88       assert(!isEnd());
     89       setupBuffer();
     90     }
     91   }
     92 
     93   /// Add a token.
     94   /// Returns true on success, false if there are no interesting tokens to
     95   /// fetch from lexer.
     96   bool addToken() {
     97     if (NoMoreInterestingTokens)
     98       return false;
     99 
    100     if (P.Tok.is(tok::newline)) {
    101       // If we see a single newline token between text tokens, skip it.
    102       Token Newline = P.Tok;
    103       P.consumeToken();
    104       if (P.Tok.isNot(tok::text)) {
    105         P.putBack(Newline);
    106         NoMoreInterestingTokens = true;
    107         return false;
    108       }
    109     }
    110     if (P.Tok.isNot(tok::text)) {
    111       NoMoreInterestingTokens = true;
    112       return false;
    113     }
    114 
    115     Toks.push_back(P.Tok);
    116     P.consumeToken();
    117     if (Toks.size() == 1)
    118       setupBuffer();
    119     return true;
    120   }
    121 
    122   void consumeWhitespace() {
    123     while (!isEnd()) {
    124       if (isWhitespace(peek()))
    125         consumeChar();
    126       else
    127         break;
    128     }
    129   }
    130 
    131   void formTokenWithChars(Token &Result,
    132                           SourceLocation Loc,
    133                           const char *TokBegin,
    134                           unsigned TokLength,
    135                           StringRef Text) {
    136     Result.setLocation(Loc);
    137     Result.setKind(tok::text);
    138     Result.setLength(TokLength);
    139 #ifndef NDEBUG
    140     Result.TextPtr = "<UNSET>";
    141     Result.IntVal = 7;
    142 #endif
    143     Result.setText(Text);
    144   }
    145 
    146 public:
    147   TextTokenRetokenizer(llvm::BumpPtrAllocator &Allocator, Parser &P):
    148       Allocator(Allocator), P(P), NoMoreInterestingTokens(false) {
    149     Pos.CurToken = 0;
    150     addToken();
    151   }
    152 
    153   /// Extract a word -- sequence of non-whitespace characters.
    154   bool lexWord(Token &Tok) {
    155     if (isEnd())
    156       return false;
    157 
    158     Position SavedPos = Pos;
    159 
    160     consumeWhitespace();
    161     SmallString<32> WordText;
    162     const char *WordBegin = Pos.BufferPtr;
    163     SourceLocation Loc = getSourceLocation();
    164     while (!isEnd()) {
    165       const char C = peek();
    166       if (!isWhitespace(C)) {
    167         WordText.push_back(C);
    168         consumeChar();
    169       } else
    170         break;
    171     }
    172     const unsigned Length = WordText.size();
    173     if (Length == 0) {
    174       Pos = SavedPos;
    175       return false;
    176     }
    177 
    178     char *TextPtr = Allocator.Allocate<char>(Length + 1);
    179 
    180     memcpy(TextPtr, WordText.c_str(), Length + 1);
    181     StringRef Text = StringRef(TextPtr, Length);
    182 
    183     formTokenWithChars(Tok, Loc, WordBegin, Length, Text);
    184     return true;
    185   }
    186 
    187   bool lexDelimitedSeq(Token &Tok, char OpenDelim, char CloseDelim) {
    188     if (isEnd())
    189       return false;
    190 
    191     Position SavedPos = Pos;
    192 
    193     consumeWhitespace();
    194     SmallString<32> WordText;
    195     const char *WordBegin = Pos.BufferPtr;
    196     SourceLocation Loc = getSourceLocation();
    197     bool Error = false;
    198     if (!isEnd()) {
    199       const char C = peek();
    200       if (C == OpenDelim) {
    201         WordText.push_back(C);
    202         consumeChar();
    203       } else
    204         Error = true;
    205     }
    206     char C = '\0';
    207     while (!Error && !isEnd()) {
    208       C = peek();
    209       WordText.push_back(C);
    210       consumeChar();
    211       if (C == CloseDelim)
    212         break;
    213     }
    214     if (!Error && C != CloseDelim)
    215       Error = true;
    216 
    217     if (Error) {
    218       Pos = SavedPos;
    219       return false;
    220     }
    221 
    222     const unsigned Length = WordText.size();
    223     char *TextPtr = Allocator.Allocate<char>(Length + 1);
    224 
    225     memcpy(TextPtr, WordText.c_str(), Length + 1);
    226     StringRef Text = StringRef(TextPtr, Length);
    227 
    228     formTokenWithChars(Tok, Loc, WordBegin,
    229                        Pos.BufferPtr - WordBegin, Text);
    230     return true;
    231   }
    232 
    233   /// Put back tokens that we didn't consume.
    234   void putBackLeftoverTokens() {
    235     if (isEnd())
    236       return;
    237 
    238     bool HavePartialTok = false;
    239     Token PartialTok;
    240     if (Pos.BufferPtr != Pos.BufferStart) {
    241       formTokenWithChars(PartialTok, getSourceLocation(),
    242                          Pos.BufferPtr, Pos.BufferEnd - Pos.BufferPtr,
    243                          StringRef(Pos.BufferPtr,
    244                                    Pos.BufferEnd - Pos.BufferPtr));
    245       HavePartialTok = true;
    246       Pos.CurToken++;
    247     }
    248 
    249     P.putBack(llvm::makeArrayRef(Toks.begin() + Pos.CurToken, Toks.end()));
    250     Pos.CurToken = Toks.size();
    251 
    252     if (HavePartialTok)
    253       P.putBack(PartialTok);
    254   }
    255 };
    256 
    257 Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator,
    258                const SourceManager &SourceMgr, DiagnosticsEngine &Diags,
    259                const CommandTraits &Traits):
    260     L(L), S(S), Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags),
    261     Traits(Traits) {
    262   consumeToken();
    263 }
    264 
    265 void Parser::parseParamCommandArgs(ParamCommandComment *PC,
    266                                    TextTokenRetokenizer &Retokenizer) {
    267   Token Arg;
    268   // Check if argument looks like direction specification: [dir]
    269   // e.g., [in], [out], [in,out]
    270   if (Retokenizer.lexDelimitedSeq(Arg, '[', ']'))
    271     S.actOnParamCommandDirectionArg(PC,
    272                                     Arg.getLocation(),
    273                                     Arg.getEndLocation(),
    274                                     Arg.getText());
    275 
    276   if (Retokenizer.lexWord(Arg))
    277     S.actOnParamCommandParamNameArg(PC,
    278                                     Arg.getLocation(),
    279                                     Arg.getEndLocation(),
    280                                     Arg.getText());
    281 }
    282 
    283 void Parser::parseTParamCommandArgs(TParamCommandComment *TPC,
    284                                     TextTokenRetokenizer &Retokenizer) {
    285   Token Arg;
    286   if (Retokenizer.lexWord(Arg))
    287     S.actOnTParamCommandParamNameArg(TPC,
    288                                      Arg.getLocation(),
    289                                      Arg.getEndLocation(),
    290                                      Arg.getText());
    291 }
    292 
    293 void Parser::parseBlockCommandArgs(BlockCommandComment *BC,
    294                                    TextTokenRetokenizer &Retokenizer,
    295                                    unsigned NumArgs) {
    296   typedef BlockCommandComment::Argument Argument;
    297   Argument *Args =
    298       new (Allocator.Allocate<Argument>(NumArgs)) Argument[NumArgs];
    299   unsigned ParsedArgs = 0;
    300   Token Arg;
    301   while (ParsedArgs < NumArgs && Retokenizer.lexWord(Arg)) {
    302     Args[ParsedArgs] = Argument(SourceRange(Arg.getLocation(),
    303                                             Arg.getEndLocation()),
    304                                 Arg.getText());
    305     ParsedArgs++;
    306   }
    307 
    308   S.actOnBlockCommandArgs(BC, llvm::makeArrayRef(Args, ParsedArgs));
    309 }
    310 
    311 BlockCommandComment *Parser::parseBlockCommand() {
    312   assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));
    313 
    314   ParamCommandComment *PC = nullptr;
    315   TParamCommandComment *TPC = nullptr;
    316   BlockCommandComment *BC = nullptr;
    317   const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
    318   CommandMarkerKind CommandMarker =
    319       Tok.is(tok::backslash_command) ? CMK_Backslash : CMK_At;
    320   if (Info->IsParamCommand) {
    321     PC = S.actOnParamCommandStart(Tok.getLocation(),
    322                                   Tok.getEndLocation(),
    323                                   Tok.getCommandID(),
    324                                   CommandMarker);
    325   } else if (Info->IsTParamCommand) {
    326     TPC = S.actOnTParamCommandStart(Tok.getLocation(),
    327                                     Tok.getEndLocation(),
    328                                     Tok.getCommandID(),
    329                                     CommandMarker);
    330   } else {
    331     BC = S.actOnBlockCommandStart(Tok.getLocation(),
    332                                   Tok.getEndLocation(),
    333                                   Tok.getCommandID(),
    334                                   CommandMarker);
    335   }
    336   consumeToken();
    337 
    338   if (isTokBlockCommand()) {
    339     // Block command ahead.  We can't nest block commands, so pretend that this
    340     // command has an empty argument.
    341     ParagraphComment *Paragraph = S.actOnParagraphComment(None);
    342     if (PC) {
    343       S.actOnParamCommandFinish(PC, Paragraph);
    344       return PC;
    345     } else if (TPC) {
    346       S.actOnTParamCommandFinish(TPC, Paragraph);
    347       return TPC;
    348     } else {
    349       S.actOnBlockCommandFinish(BC, Paragraph);
    350       return BC;
    351     }
    352   }
    353 
    354   if (PC || TPC || Info->NumArgs > 0) {
    355     // In order to parse command arguments we need to retokenize a few
    356     // following text tokens.
    357     TextTokenRetokenizer Retokenizer(Allocator, *this);
    358 
    359     if (PC)
    360       parseParamCommandArgs(PC, Retokenizer);
    361     else if (TPC)
    362       parseTParamCommandArgs(TPC, Retokenizer);
    363     else
    364       parseBlockCommandArgs(BC, Retokenizer, Info->NumArgs);
    365 
    366     Retokenizer.putBackLeftoverTokens();
    367   }
    368 
    369   // If there's a block command ahead, we will attach an empty paragraph to
    370   // this command.
    371   bool EmptyParagraph = false;
    372   if (isTokBlockCommand())
    373     EmptyParagraph = true;
    374   else if (Tok.is(tok::newline)) {
    375     Token PrevTok = Tok;
    376     consumeToken();
    377     EmptyParagraph = isTokBlockCommand();
    378     putBack(PrevTok);
    379   }
    380 
    381   ParagraphComment *Paragraph;
    382   if (EmptyParagraph)
    383     Paragraph = S.actOnParagraphComment(None);
    384   else {
    385     BlockContentComment *Block = parseParagraphOrBlockCommand();
    386     // Since we have checked for a block command, we should have parsed a
    387     // paragraph.
    388     Paragraph = cast<ParagraphComment>(Block);
    389   }
    390 
    391   if (PC) {
    392     S.actOnParamCommandFinish(PC, Paragraph);
    393     return PC;
    394   } else if (TPC) {
    395     S.actOnTParamCommandFinish(TPC, Paragraph);
    396     return TPC;
    397   } else {
    398     S.actOnBlockCommandFinish(BC, Paragraph);
    399     return BC;
    400   }
    401 }
    402 
    403 InlineCommandComment *Parser::parseInlineCommand() {
    404   assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));
    405 
    406   const Token CommandTok = Tok;
    407   consumeToken();
    408 
    409   TextTokenRetokenizer Retokenizer(Allocator, *this);
    410 
    411   Token ArgTok;
    412   bool ArgTokValid = Retokenizer.lexWord(ArgTok);
    413 
    414   InlineCommandComment *IC;
    415   if (ArgTokValid) {
    416     IC = S.actOnInlineCommand(CommandTok.getLocation(),
    417                               CommandTok.getEndLocation(),
    418                               CommandTok.getCommandID(),
    419                               ArgTok.getLocation(),
    420                               ArgTok.getEndLocation(),
    421                               ArgTok.getText());
    422   } else {
    423     IC = S.actOnInlineCommand(CommandTok.getLocation(),
    424                               CommandTok.getEndLocation(),
    425                               CommandTok.getCommandID());
    426   }
    427 
    428   Retokenizer.putBackLeftoverTokens();
    429 
    430   return IC;
    431 }
    432 
    433 HTMLStartTagComment *Parser::parseHTMLStartTag() {
    434   assert(Tok.is(tok::html_start_tag));
    435   HTMLStartTagComment *HST =
    436       S.actOnHTMLStartTagStart(Tok.getLocation(),
    437                                Tok.getHTMLTagStartName());
    438   consumeToken();
    439 
    440   SmallVector<HTMLStartTagComment::Attribute, 2> Attrs;
    441   while (true) {
    442     switch (Tok.getKind()) {
    443     case tok::html_ident: {
    444       Token Ident = Tok;
    445       consumeToken();
    446       if (Tok.isNot(tok::html_equals)) {
    447         Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),
    448                                                        Ident.getHTMLIdent()));
    449         continue;
    450       }
    451       Token Equals = Tok;
    452       consumeToken();
    453       if (Tok.isNot(tok::html_quoted_string)) {
    454         Diag(Tok.getLocation(),
    455              diag::warn_doc_html_start_tag_expected_quoted_string)
    456           << SourceRange(Equals.getLocation());
    457         Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),
    458                                                        Ident.getHTMLIdent()));
    459         while (Tok.is(tok::html_equals) ||
    460                Tok.is(tok::html_quoted_string))
    461           consumeToken();
    462         continue;
    463       }
    464       Attrs.push_back(HTMLStartTagComment::Attribute(
    465                               Ident.getLocation(),
    466                               Ident.getHTMLIdent(),
    467                               Equals.getLocation(),
    468                               SourceRange(Tok.getLocation(),
    469                                           Tok.getEndLocation()),
    470                               Tok.getHTMLQuotedString()));
    471       consumeToken();
    472       continue;
    473     }
    474 
    475     case tok::html_greater:
    476       S.actOnHTMLStartTagFinish(HST,
    477                                 S.copyArray(llvm::makeArrayRef(Attrs)),
    478                                 Tok.getLocation(),
    479                                 /* IsSelfClosing = */ false);
    480       consumeToken();
    481       return HST;
    482 
    483     case tok::html_slash_greater:
    484       S.actOnHTMLStartTagFinish(HST,
    485                                 S.copyArray(llvm::makeArrayRef(Attrs)),
    486                                 Tok.getLocation(),
    487                                 /* IsSelfClosing = */ true);
    488       consumeToken();
    489       return HST;
    490 
    491     case tok::html_equals:
    492     case tok::html_quoted_string:
    493       Diag(Tok.getLocation(),
    494            diag::warn_doc_html_start_tag_expected_ident_or_greater);
    495       while (Tok.is(tok::html_equals) ||
    496              Tok.is(tok::html_quoted_string))
    497         consumeToken();
    498       if (Tok.is(tok::html_ident) ||
    499           Tok.is(tok::html_greater) ||
    500           Tok.is(tok::html_slash_greater))
    501         continue;
    502 
    503       S.actOnHTMLStartTagFinish(HST,
    504                                 S.copyArray(llvm::makeArrayRef(Attrs)),
    505                                 SourceLocation(),
    506                                 /* IsSelfClosing = */ false);
    507       return HST;
    508 
    509     default:
    510       // Not a token from an HTML start tag.  Thus HTML tag prematurely ended.
    511       S.actOnHTMLStartTagFinish(HST,
    512                                 S.copyArray(llvm::makeArrayRef(Attrs)),
    513                                 SourceLocation(),
    514                                 /* IsSelfClosing = */ false);
    515       bool StartLineInvalid;
    516       const unsigned StartLine = SourceMgr.getPresumedLineNumber(
    517                                                   HST->getLocation(),
    518                                                   &StartLineInvalid);
    519       bool EndLineInvalid;
    520       const unsigned EndLine = SourceMgr.getPresumedLineNumber(
    521                                                   Tok.getLocation(),
    522                                                   &EndLineInvalid);
    523       if (StartLineInvalid || EndLineInvalid || StartLine == EndLine)
    524         Diag(Tok.getLocation(),
    525              diag::warn_doc_html_start_tag_expected_ident_or_greater)
    526           << HST->getSourceRange();
    527       else {
    528         Diag(Tok.getLocation(),
    529              diag::warn_doc_html_start_tag_expected_ident_or_greater);
    530         Diag(HST->getLocation(), diag::note_doc_html_tag_started_here)
    531           << HST->getSourceRange();
    532       }
    533       return HST;
    534     }
    535   }
    536 }
    537 
    538 HTMLEndTagComment *Parser::parseHTMLEndTag() {
    539   assert(Tok.is(tok::html_end_tag));
    540   Token TokEndTag = Tok;
    541   consumeToken();
    542   SourceLocation Loc;
    543   if (Tok.is(tok::html_greater)) {
    544     Loc = Tok.getLocation();
    545     consumeToken();
    546   }
    547 
    548   return S.actOnHTMLEndTag(TokEndTag.getLocation(),
    549                            Loc,
    550                            TokEndTag.getHTMLTagEndName());
    551 }
    552 
    553 BlockContentComment *Parser::parseParagraphOrBlockCommand() {
    554   SmallVector<InlineContentComment *, 8> Content;
    555 
    556   while (true) {
    557     switch (Tok.getKind()) {
    558     case tok::verbatim_block_begin:
    559     case tok::verbatim_line_name:
    560     case tok::eof:
    561       assert(Content.size() != 0);
    562       break; // Block content or EOF ahead, finish this parapgaph.
    563 
    564     case tok::unknown_command:
    565       Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
    566                                               Tok.getEndLocation(),
    567                                               Tok.getUnknownCommandName()));
    568       consumeToken();
    569       continue;
    570 
    571     case tok::backslash_command:
    572     case tok::at_command: {
    573       const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
    574       if (Info->IsBlockCommand) {
    575         if (Content.size() == 0)
    576           return parseBlockCommand();
    577         break; // Block command ahead, finish this parapgaph.
    578       }
    579       if (Info->IsVerbatimBlockEndCommand) {
    580         Diag(Tok.getLocation(),
    581              diag::warn_verbatim_block_end_without_start)
    582           << Tok.is(tok::at_command)
    583           << Info->Name
    584           << SourceRange(Tok.getLocation(), Tok.getEndLocation());
    585         consumeToken();
    586         continue;
    587       }
    588       if (Info->IsUnknownCommand) {
    589         Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
    590                                                 Tok.getEndLocation(),
    591                                                 Info->getID()));
    592         consumeToken();
    593         continue;
    594       }
    595       assert(Info->IsInlineCommand);
    596       Content.push_back(parseInlineCommand());
    597       continue;
    598     }
    599 
    600     case tok::newline: {
    601       consumeToken();
    602       if (Tok.is(tok::newline) || Tok.is(tok::eof)) {
    603         consumeToken();
    604         break; // Two newlines -- end of paragraph.
    605       }
    606       // Also allow [tok::newline, tok::text, tok::newline] if the middle
    607       // tok::text is just whitespace.
    608       if (Tok.is(tok::text) && isWhitespace(Tok.getText())) {
    609         Token WhitespaceTok = Tok;
    610         consumeToken();
    611         if (Tok.is(tok::newline) || Tok.is(tok::eof)) {
    612           consumeToken();
    613           break;
    614         }
    615         // We have [tok::newline, tok::text, non-newline].  Put back tok::text.
    616         putBack(WhitespaceTok);
    617       }
    618       if (Content.size() > 0)
    619         Content.back()->addTrailingNewline();
    620       continue;
    621     }
    622 
    623     // Don't deal with HTML tag soup now.
    624     case tok::html_start_tag:
    625       Content.push_back(parseHTMLStartTag());
    626       continue;
    627 
    628     case tok::html_end_tag:
    629       Content.push_back(parseHTMLEndTag());
    630       continue;
    631 
    632     case tok::text:
    633       Content.push_back(S.actOnText(Tok.getLocation(),
    634                                     Tok.getEndLocation(),
    635                                     Tok.getText()));
    636       consumeToken();
    637       continue;
    638 
    639     case tok::verbatim_block_line:
    640     case tok::verbatim_block_end:
    641     case tok::verbatim_line_text:
    642     case tok::html_ident:
    643     case tok::html_equals:
    644     case tok::html_quoted_string:
    645     case tok::html_greater:
    646     case tok::html_slash_greater:
    647       llvm_unreachable("should not see this token");
    648     }
    649     break;
    650   }
    651 
    652   return S.actOnParagraphComment(S.copyArray(llvm::makeArrayRef(Content)));
    653 }
    654 
    655 VerbatimBlockComment *Parser::parseVerbatimBlock() {
    656   assert(Tok.is(tok::verbatim_block_begin));
    657 
    658   VerbatimBlockComment *VB =
    659       S.actOnVerbatimBlockStart(Tok.getLocation(),
    660                                 Tok.getVerbatimBlockID());
    661   consumeToken();
    662 
    663   // Don't create an empty line if verbatim opening command is followed
    664   // by a newline.
    665   if (Tok.is(tok::newline))
    666     consumeToken();
    667 
    668   SmallVector<VerbatimBlockLineComment *, 8> Lines;
    669   while (Tok.is(tok::verbatim_block_line) ||
    670          Tok.is(tok::newline)) {
    671     VerbatimBlockLineComment *Line;
    672     if (Tok.is(tok::verbatim_block_line)) {
    673       Line = S.actOnVerbatimBlockLine(Tok.getLocation(),
    674                                       Tok.getVerbatimBlockText());
    675       consumeToken();
    676       if (Tok.is(tok::newline)) {
    677         consumeToken();
    678       }
    679     } else {
    680       // Empty line, just a tok::newline.
    681       Line = S.actOnVerbatimBlockLine(Tok.getLocation(), "");
    682       consumeToken();
    683     }
    684     Lines.push_back(Line);
    685   }
    686 
    687   if (Tok.is(tok::verbatim_block_end)) {
    688     const CommandInfo *Info = Traits.getCommandInfo(Tok.getVerbatimBlockID());
    689     S.actOnVerbatimBlockFinish(VB, Tok.getLocation(),
    690                                Info->Name,
    691                                S.copyArray(llvm::makeArrayRef(Lines)));
    692     consumeToken();
    693   } else {
    694     // Unterminated \\verbatim block
    695     S.actOnVerbatimBlockFinish(VB, SourceLocation(), "",
    696                                S.copyArray(llvm::makeArrayRef(Lines)));
    697   }
    698 
    699   return VB;
    700 }
    701 
    702 VerbatimLineComment *Parser::parseVerbatimLine() {
    703   assert(Tok.is(tok::verbatim_line_name));
    704 
    705   Token NameTok = Tok;
    706   consumeToken();
    707 
    708   SourceLocation TextBegin;
    709   StringRef Text;
    710   // Next token might not be a tok::verbatim_line_text if verbatim line
    711   // starting command comes just before a newline or comment end.
    712   if (Tok.is(tok::verbatim_line_text)) {
    713     TextBegin = Tok.getLocation();
    714     Text = Tok.getVerbatimLineText();
    715   } else {
    716     TextBegin = NameTok.getEndLocation();
    717     Text = "";
    718   }
    719 
    720   VerbatimLineComment *VL = S.actOnVerbatimLine(NameTok.getLocation(),
    721                                                 NameTok.getVerbatimLineID(),
    722                                                 TextBegin,
    723                                                 Text);
    724   consumeToken();
    725   return VL;
    726 }
    727 
    728 BlockContentComment *Parser::parseBlockContent() {
    729   switch (Tok.getKind()) {
    730   case tok::text:
    731   case tok::unknown_command:
    732   case tok::backslash_command:
    733   case tok::at_command:
    734   case tok::html_start_tag:
    735   case tok::html_end_tag:
    736     return parseParagraphOrBlockCommand();
    737 
    738   case tok::verbatim_block_begin:
    739     return parseVerbatimBlock();
    740 
    741   case tok::verbatim_line_name:
    742     return parseVerbatimLine();
    743 
    744   case tok::eof:
    745   case tok::newline:
    746   case tok::verbatim_block_line:
    747   case tok::verbatim_block_end:
    748   case tok::verbatim_line_text:
    749   case tok::html_ident:
    750   case tok::html_equals:
    751   case tok::html_quoted_string:
    752   case tok::html_greater:
    753   case tok::html_slash_greater:
    754     llvm_unreachable("should not see this token");
    755   }
    756   llvm_unreachable("bogus token kind");
    757 }
    758 
    759 FullComment *Parser::parseFullComment() {
    760   // Skip newlines at the beginning of the comment.
    761   while (Tok.is(tok::newline))
    762     consumeToken();
    763 
    764   SmallVector<BlockContentComment *, 8> Blocks;
    765   while (Tok.isNot(tok::eof)) {
    766     Blocks.push_back(parseBlockContent());
    767 
    768     // Skip extra newlines after paragraph end.
    769     while (Tok.is(tok::newline))
    770       consumeToken();
    771   }
    772   return S.actOnFullComment(S.copyArray(llvm::makeArrayRef(Blocks)));
    773 }
    774 
    775 } // end namespace comments
    776 } // end namespace clang
    777