xref: /llvm-project/clang/lib/AST/CommentParser.cpp (revision d49963609d58ceaf85de6ad90170a359258a3a2c)
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::command));
304 
305   ParamCommandComment *PC;
306   TParamCommandComment *TPC;
307   BlockCommandComment *BC;
308   bool IsParam = false;
309   bool IsTParam = false;
310   const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
311   if (Info->IsParamCommand) {
312     IsParam = true;
313     PC = S.actOnParamCommandStart(Tok.getLocation(),
314                                   Tok.getEndLocation(),
315                                   Tok.getCommandID());
316     PC->setHDCommand(Tok.getHDCommand());
317   } else if (Info->IsTParamCommand) {
318     IsTParam = true;
319     TPC = S.actOnTParamCommandStart(Tok.getLocation(),
320                                     Tok.getEndLocation(),
321                                     Tok.getCommandID());
322     TPC->setHDCommand(Tok.getHDCommand());
323   } else {
324     BC = S.actOnBlockCommandStart(Tok.getLocation(),
325                                   Tok.getEndLocation(),
326                                   Tok.getCommandID());
327     BC->setHDCommand(Tok.getHDCommand());
328   }
329   consumeToken();
330 
331   if (isTokBlockCommand()) {
332     // Block command ahead.  We can't nest block commands, so pretend that this
333     // command has an empty argument.
334     ParagraphComment *Paragraph = S.actOnParagraphComment(
335                                 ArrayRef<InlineContentComment *>());
336     if (IsParam) {
337       S.actOnParamCommandFinish(PC, Paragraph);
338       return PC;
339     } else if (IsTParam) {
340       S.actOnTParamCommandFinish(TPC, Paragraph);
341       return TPC;
342     } else {
343       S.actOnBlockCommandFinish(BC, Paragraph);
344       return BC;
345     }
346   }
347 
348   if (IsParam || IsTParam || Info->NumArgs > 0) {
349     // In order to parse command arguments we need to retokenize a few
350     // following text tokens.
351     TextTokenRetokenizer Retokenizer(Allocator, *this);
352 
353     if (IsParam)
354       parseParamCommandArgs(PC, Retokenizer);
355     else if (IsTParam)
356       parseTParamCommandArgs(TPC, Retokenizer);
357     else
358       parseBlockCommandArgs(BC, Retokenizer, Info->NumArgs);
359 
360     Retokenizer.putBackLeftoverTokens();
361   }
362 
363   // If there's a block command ahead, we will attach an empty paragraph to
364   // this command.
365   bool EmptyParagraph = false;
366   if (isTokBlockCommand())
367     EmptyParagraph = true;
368   else if (Tok.is(tok::newline)) {
369     Token PrevTok = Tok;
370     consumeToken();
371     EmptyParagraph = isTokBlockCommand();
372     putBack(PrevTok);
373   }
374 
375   ParagraphComment *Paragraph;
376   if (EmptyParagraph)
377     Paragraph = S.actOnParagraphComment(ArrayRef<InlineContentComment *>());
378   else {
379     BlockContentComment *Block = parseParagraphOrBlockCommand();
380     // Since we have checked for a block command, we should have parsed a
381     // paragraph.
382     Paragraph = cast<ParagraphComment>(Block);
383   }
384 
385   if (IsParam) {
386     S.actOnParamCommandFinish(PC, Paragraph);
387     return PC;
388   } else if (IsTParam) {
389     S.actOnTParamCommandFinish(TPC, Paragraph);
390     return TPC;
391   } else {
392     S.actOnBlockCommandFinish(BC, Paragraph);
393     return BC;
394   }
395 }
396 
397 InlineCommandComment *Parser::parseInlineCommand() {
398   assert(Tok.is(tok::command));
399 
400   const Token CommandTok = Tok;
401   consumeToken();
402 
403   TextTokenRetokenizer Retokenizer(Allocator, *this);
404 
405   Token ArgTok;
406   bool ArgTokValid = Retokenizer.lexWord(ArgTok);
407 
408   InlineCommandComment *IC;
409   if (ArgTokValid) {
410     IC = S.actOnInlineCommand(CommandTok.getLocation(),
411                               CommandTok.getEndLocation(),
412                               CommandTok.getCommandID(),
413                               ArgTok.getLocation(),
414                               ArgTok.getEndLocation(),
415                               ArgTok.getText());
416   } else {
417     IC = S.actOnInlineCommand(CommandTok.getLocation(),
418                               CommandTok.getEndLocation(),
419                               CommandTok.getCommandID());
420   }
421 
422   Retokenizer.putBackLeftoverTokens();
423 
424   return IC;
425 }
426 
427 HTMLStartTagComment *Parser::parseHTMLStartTag() {
428   assert(Tok.is(tok::html_start_tag));
429   HTMLStartTagComment *HST =
430       S.actOnHTMLStartTagStart(Tok.getLocation(),
431                                Tok.getHTMLTagStartName());
432   consumeToken();
433 
434   SmallVector<HTMLStartTagComment::Attribute, 2> Attrs;
435   while (true) {
436     switch (Tok.getKind()) {
437     case tok::html_ident: {
438       Token Ident = Tok;
439       consumeToken();
440       if (Tok.isNot(tok::html_equals)) {
441         Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),
442                                                        Ident.getHTMLIdent()));
443         continue;
444       }
445       Token Equals = Tok;
446       consumeToken();
447       if (Tok.isNot(tok::html_quoted_string)) {
448         Diag(Tok.getLocation(),
449              diag::warn_doc_html_start_tag_expected_quoted_string)
450           << SourceRange(Equals.getLocation());
451         Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),
452                                                        Ident.getHTMLIdent()));
453         while (Tok.is(tok::html_equals) ||
454                Tok.is(tok::html_quoted_string))
455           consumeToken();
456         continue;
457       }
458       Attrs.push_back(HTMLStartTagComment::Attribute(
459                               Ident.getLocation(),
460                               Ident.getHTMLIdent(),
461                               Equals.getLocation(),
462                               SourceRange(Tok.getLocation(),
463                                           Tok.getEndLocation()),
464                               Tok.getHTMLQuotedString()));
465       consumeToken();
466       continue;
467     }
468 
469     case tok::html_greater:
470       S.actOnHTMLStartTagFinish(HST,
471                                 S.copyArray(llvm::makeArrayRef(Attrs)),
472                                 Tok.getLocation(),
473                                 /* IsSelfClosing = */ false);
474       consumeToken();
475       return HST;
476 
477     case tok::html_slash_greater:
478       S.actOnHTMLStartTagFinish(HST,
479                                 S.copyArray(llvm::makeArrayRef(Attrs)),
480                                 Tok.getLocation(),
481                                 /* IsSelfClosing = */ true);
482       consumeToken();
483       return HST;
484 
485     case tok::html_equals:
486     case tok::html_quoted_string:
487       Diag(Tok.getLocation(),
488            diag::warn_doc_html_start_tag_expected_ident_or_greater);
489       while (Tok.is(tok::html_equals) ||
490              Tok.is(tok::html_quoted_string))
491         consumeToken();
492       if (Tok.is(tok::html_ident) ||
493           Tok.is(tok::html_greater) ||
494           Tok.is(tok::html_slash_greater))
495         continue;
496 
497       S.actOnHTMLStartTagFinish(HST,
498                                 S.copyArray(llvm::makeArrayRef(Attrs)),
499                                 SourceLocation(),
500                                 /* IsSelfClosing = */ false);
501       return HST;
502 
503     default:
504       // Not a token from an HTML start tag.  Thus HTML tag prematurely ended.
505       S.actOnHTMLStartTagFinish(HST,
506                                 S.copyArray(llvm::makeArrayRef(Attrs)),
507                                 SourceLocation(),
508                                 /* IsSelfClosing = */ false);
509       bool StartLineInvalid;
510       const unsigned StartLine = SourceMgr.getPresumedLineNumber(
511                                                   HST->getLocation(),
512                                                   &StartLineInvalid);
513       bool EndLineInvalid;
514       const unsigned EndLine = SourceMgr.getPresumedLineNumber(
515                                                   Tok.getLocation(),
516                                                   &EndLineInvalid);
517       if (StartLineInvalid || EndLineInvalid || StartLine == EndLine)
518         Diag(Tok.getLocation(),
519              diag::warn_doc_html_start_tag_expected_ident_or_greater)
520           << HST->getSourceRange();
521       else {
522         Diag(Tok.getLocation(),
523              diag::warn_doc_html_start_tag_expected_ident_or_greater);
524         Diag(HST->getLocation(), diag::note_doc_html_tag_started_here)
525           << HST->getSourceRange();
526       }
527       return HST;
528     }
529   }
530 }
531 
532 HTMLEndTagComment *Parser::parseHTMLEndTag() {
533   assert(Tok.is(tok::html_end_tag));
534   Token TokEndTag = Tok;
535   consumeToken();
536   SourceLocation Loc;
537   if (Tok.is(tok::html_greater)) {
538     Loc = Tok.getLocation();
539     consumeToken();
540   }
541 
542   return S.actOnHTMLEndTag(TokEndTag.getLocation(),
543                            Loc,
544                            TokEndTag.getHTMLTagEndName());
545 }
546 
547 BlockContentComment *Parser::parseParagraphOrBlockCommand() {
548   SmallVector<InlineContentComment *, 8> Content;
549 
550   while (true) {
551     switch (Tok.getKind()) {
552     case tok::verbatim_block_begin:
553     case tok::verbatim_line_name:
554     case tok::eof:
555       assert(Content.size() != 0);
556       break; // Block content or EOF ahead, finish this parapgaph.
557 
558     case tok::unknown_command:
559       Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
560                                               Tok.getEndLocation(),
561                                               Tok.getUnknownCommandName()));
562       consumeToken();
563       continue;
564 
565     case tok::command: {
566       const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
567       if (Info->IsBlockCommand) {
568         if (Content.size() == 0)
569           return parseBlockCommand();
570         break; // Block command ahead, finish this parapgaph.
571       }
572       if (Info->IsVerbatimBlockEndCommand) {
573         Diag(Tok.getLocation(),
574              diag::warn_verbatim_block_end_without_start)
575           << Tok.getHDCommand()
576           << Info->Name
577           << SourceRange(Tok.getLocation(), Tok.getEndLocation());
578         consumeToken();
579         continue;
580       }
581       if (Info->IsUnknownCommand) {
582         Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
583                                                 Tok.getEndLocation(),
584                                                 Info->getID()));
585         consumeToken();
586         continue;
587       }
588       assert(Info->IsInlineCommand);
589       Content.push_back(parseInlineCommand());
590       continue;
591     }
592 
593     case tok::newline: {
594       consumeToken();
595       if (Tok.is(tok::newline) || Tok.is(tok::eof)) {
596         consumeToken();
597         break; // Two newlines -- end of paragraph.
598       }
599       if (Content.size() > 0)
600         Content.back()->addTrailingNewline();
601       continue;
602     }
603 
604     // Don't deal with HTML tag soup now.
605     case tok::html_start_tag:
606       Content.push_back(parseHTMLStartTag());
607       continue;
608 
609     case tok::html_end_tag:
610       Content.push_back(parseHTMLEndTag());
611       continue;
612 
613     case tok::text:
614       Content.push_back(S.actOnText(Tok.getLocation(),
615                                     Tok.getEndLocation(),
616                                     Tok.getText()));
617       consumeToken();
618       continue;
619 
620     case tok::verbatim_block_line:
621     case tok::verbatim_block_end:
622     case tok::verbatim_line_text:
623     case tok::html_ident:
624     case tok::html_equals:
625     case tok::html_quoted_string:
626     case tok::html_greater:
627     case tok::html_slash_greater:
628       llvm_unreachable("should not see this token");
629     }
630     break;
631   }
632 
633   return S.actOnParagraphComment(S.copyArray(llvm::makeArrayRef(Content)));
634 }
635 
636 VerbatimBlockComment *Parser::parseVerbatimBlock() {
637   assert(Tok.is(tok::verbatim_block_begin));
638 
639   VerbatimBlockComment *VB =
640       S.actOnVerbatimBlockStart(Tok.getLocation(),
641                                 Tok.getVerbatimBlockID());
642   consumeToken();
643 
644   // Don't create an empty line if verbatim opening command is followed
645   // by a newline.
646   if (Tok.is(tok::newline))
647     consumeToken();
648 
649   SmallVector<VerbatimBlockLineComment *, 8> Lines;
650   while (Tok.is(tok::verbatim_block_line) ||
651          Tok.is(tok::newline)) {
652     VerbatimBlockLineComment *Line;
653     if (Tok.is(tok::verbatim_block_line)) {
654       Line = S.actOnVerbatimBlockLine(Tok.getLocation(),
655                                       Tok.getVerbatimBlockText());
656       consumeToken();
657       if (Tok.is(tok::newline)) {
658         consumeToken();
659       }
660     } else {
661       // Empty line, just a tok::newline.
662       Line = S.actOnVerbatimBlockLine(Tok.getLocation(), "");
663       consumeToken();
664     }
665     Lines.push_back(Line);
666   }
667 
668   if (Tok.is(tok::verbatim_block_end)) {
669     const CommandInfo *Info = Traits.getCommandInfo(Tok.getVerbatimBlockID());
670     S.actOnVerbatimBlockFinish(VB, Tok.getLocation(),
671                                Info->Name,
672                                S.copyArray(llvm::makeArrayRef(Lines)));
673     consumeToken();
674   } else {
675     // Unterminated \\verbatim block
676     S.actOnVerbatimBlockFinish(VB, SourceLocation(), "",
677                                S.copyArray(llvm::makeArrayRef(Lines)));
678   }
679 
680   return VB;
681 }
682 
683 VerbatimLineComment *Parser::parseVerbatimLine() {
684   assert(Tok.is(tok::verbatim_line_name));
685 
686   Token NameTok = Tok;
687   consumeToken();
688 
689   SourceLocation TextBegin;
690   StringRef Text;
691   // Next token might not be a tok::verbatim_line_text if verbatim line
692   // starting command comes just before a newline or comment end.
693   if (Tok.is(tok::verbatim_line_text)) {
694     TextBegin = Tok.getLocation();
695     Text = Tok.getVerbatimLineText();
696   } else {
697     TextBegin = NameTok.getEndLocation();
698     Text = "";
699   }
700 
701   VerbatimLineComment *VL = S.actOnVerbatimLine(NameTok.getLocation(),
702                                                 NameTok.getVerbatimLineID(),
703                                                 TextBegin,
704                                                 Text);
705   consumeToken();
706   return VL;
707 }
708 
709 BlockContentComment *Parser::parseBlockContent() {
710   switch (Tok.getKind()) {
711   case tok::text:
712   case tok::unknown_command:
713   case tok::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