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 
isWhitespace(llvm::StringRef S)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 
isEnd() const53   bool isEnd() const {
54     return Pos.CurToken >= Toks.size();
55   }
56 
57   /// Sets up the buffer pointers to point to current token.
setupBuffer()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 
getSourceLocation() const68   SourceLocation getSourceLocation() const {
69     const unsigned CharNo = Pos.BufferPtr - Pos.BufferStart;
70     return Pos.BufferStartLoc.getLocWithOffset(CharNo);
71   }
72 
peek() const73   char peek() const {
74     assert(!isEnd());
75     assert(Pos.BufferPtr != Pos.BufferEnd);
76     return *Pos.BufferPtr;
77   }
78 
consumeChar()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.
addToken()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 
consumeWhitespace()122   void consumeWhitespace() {
123     while (!isEnd()) {
124       if (isWhitespace(peek()))
125         consumeChar();
126       else
127         break;
128     }
129   }
130 
formTokenWithChars(Token & Result,SourceLocation Loc,const char * TokBegin,unsigned TokLength,StringRef Text)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:
TextTokenRetokenizer(llvm::BumpPtrAllocator & Allocator,Parser & P)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.
lexWord(Token & Tok)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 
lexDelimitedSeq(Token & Tok,char OpenDelim,char CloseDelim)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.
putBackLeftoverTokens()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 
Parser(Lexer & L,Sema & S,llvm::BumpPtrAllocator & Allocator,const SourceManager & SourceMgr,DiagnosticsEngine & Diags,const CommandTraits & Traits)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 
parseParamCommandArgs(ParamCommandComment * PC,TextTokenRetokenizer & Retokenizer)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 
parseTParamCommandArgs(TParamCommandComment * TPC,TextTokenRetokenizer & Retokenizer)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 
parseBlockCommandArgs(BlockCommandComment * BC,TextTokenRetokenizer & Retokenizer,unsigned NumArgs)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 
parseBlockCommand()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 
parseInlineCommand()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 
parseHTMLStartTag()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 
parseHTMLEndTag()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 
parseParagraphOrBlockCommand()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 
parseVerbatimBlock()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 
parseVerbatimLine()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 
parseBlockContent()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 
parseFullComment()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