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