xref: /llvm-project/clang/lib/AST/CommentParser.cpp (revision 714693059116794ba293787437b293327b38a95d)
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/SourceManager.h"
15 #include "llvm/Support/ErrorHandling.h"
16 
17 namespace clang {
18 namespace comments {
19 
20 /// Re-lexes a sequence of tok::text tokens.
21 class TextTokenRetokenizer {
22   llvm::BumpPtrAllocator &Allocator;
23   Parser &P;
24 
25   /// This flag is set when there are no more tokens we can fetch from lexer.
26   bool NoMoreInterestingTokens;
27 
28   /// Token buffer: tokens we have processed and lookahead.
29   SmallVector<Token, 16> Toks;
30 
31   /// A position in \c Toks.
32   struct Position {
33     unsigned CurToken;
34     const char *BufferStart;
35     const char *BufferEnd;
36     const char *BufferPtr;
37     SourceLocation BufferStartLoc;
38   };
39 
40   /// Current position in Toks.
41   Position Pos;
42 
43   bool isEnd() const {
44     return Pos.CurToken >= Toks.size();
45   }
46 
47   /// Sets up the buffer pointers to point to current token.
48   void setupBuffer() {
49     assert(!isEnd());
50     const Token &Tok = Toks[Pos.CurToken];
51 
52     Pos.BufferStart = Tok.getText().begin();
53     Pos.BufferEnd = Tok.getText().end();
54     Pos.BufferPtr = Pos.BufferStart;
55     Pos.BufferStartLoc = Tok.getLocation();
56   }
57 
58   SourceLocation getSourceLocation() const {
59     const unsigned CharNo = Pos.BufferPtr - Pos.BufferStart;
60     return Pos.BufferStartLoc.getLocWithOffset(CharNo);
61   }
62 
63   char peek() const {
64     assert(!isEnd());
65     assert(Pos.BufferPtr != Pos.BufferEnd);
66     return *Pos.BufferPtr;
67   }
68 
69   void consumeChar() {
70     assert(!isEnd());
71     assert(Pos.BufferPtr != Pos.BufferEnd);
72     Pos.BufferPtr++;
73     if (Pos.BufferPtr == Pos.BufferEnd) {
74       Pos.CurToken++;
75       if (isEnd() && !addToken())
76         return;
77 
78       assert(!isEnd());
79       setupBuffer();
80     }
81   }
82 
83   /// Add a token.
84   /// Returns true on success, false if there are no interesting tokens to
85   /// fetch from lexer.
86   bool addToken() {
87     if (NoMoreInterestingTokens)
88       return false;
89 
90     if (P.Tok.is(tok::newline)) {
91       // If we see a single newline token between text tokens, skip it.
92       Token Newline = P.Tok;
93       P.consumeToken();
94       if (P.Tok.isNot(tok::text)) {
95         P.putBack(Newline);
96         NoMoreInterestingTokens = true;
97         return false;
98       }
99     }
100     if (P.Tok.isNot(tok::text)) {
101       NoMoreInterestingTokens = true;
102       return false;
103     }
104 
105     Toks.push_back(P.Tok);
106     P.consumeToken();
107     if (Toks.size() == 1)
108       setupBuffer();
109     return true;
110   }
111 
112   static bool isWhitespace(char C) {
113     return C == ' ' || C == '\n' || C == '\r' ||
114            C == '\t' || C == '\f' || C == '\v';
115   }
116 
117   void consumeWhitespace() {
118     while (!isEnd()) {
119       if (isWhitespace(peek()))
120         consumeChar();
121       else
122         break;
123     }
124   }
125 
126   void formTokenWithChars(Token &Result,
127                           SourceLocation Loc,
128                           const char *TokBegin,
129                           unsigned TokLength,
130                           StringRef Text) {
131     Result.setLocation(Loc);
132     Result.setKind(tok::text);
133     Result.setLength(TokLength);
134 #ifndef NDEBUG
135     Result.TextPtr = "<UNSET>";
136     Result.IntVal = 7;
137 #endif
138     Result.setText(Text);
139   }
140 
141 public:
142   TextTokenRetokenizer(llvm::BumpPtrAllocator &Allocator, Parser &P):
143       Allocator(Allocator), P(P), NoMoreInterestingTokens(false) {
144     Pos.CurToken = 0;
145     addToken();
146   }
147 
148   /// Extract a word -- sequence of non-whitespace characters.
149   bool lexWord(Token &Tok) {
150     if (isEnd())
151       return false;
152 
153     Position SavedPos = Pos;
154 
155     consumeWhitespace();
156     SmallString<32> WordText;
157     const char *WordBegin = Pos.BufferPtr;
158     SourceLocation Loc = getSourceLocation();
159     while (!isEnd()) {
160       const char C = peek();
161       if (!isWhitespace(C)) {
162         WordText.push_back(C);
163         consumeChar();
164       } else
165         break;
166     }
167     const unsigned Length = WordText.size();
168     if (Length == 0) {
169       Pos = SavedPos;
170       return false;
171     }
172 
173     char *TextPtr = Allocator.Allocate<char>(Length + 1);
174 
175     memcpy(TextPtr, WordText.c_str(), Length + 1);
176     StringRef Text = StringRef(TextPtr, Length);
177 
178     formTokenWithChars(Tok, Loc, WordBegin, Length, Text);
179     return true;
180   }
181 
182   bool lexDelimitedSeq(Token &Tok, char OpenDelim, char CloseDelim) {
183     if (isEnd())
184       return false;
185 
186     Position SavedPos = Pos;
187 
188     consumeWhitespace();
189     SmallString<32> WordText;
190     const char *WordBegin = Pos.BufferPtr;
191     SourceLocation Loc = getSourceLocation();
192     bool Error = false;
193     if (!isEnd()) {
194       const char C = peek();
195       if (C == OpenDelim) {
196         WordText.push_back(C);
197         consumeChar();
198       } else
199         Error = true;
200     }
201     char C = '\0';
202     while (!Error && !isEnd()) {
203       C = peek();
204       WordText.push_back(C);
205       consumeChar();
206       if (C == CloseDelim)
207         break;
208     }
209     if (!Error && C != CloseDelim)
210       Error = true;
211 
212     if (Error) {
213       Pos = SavedPos;
214       return false;
215     }
216 
217     const unsigned Length = WordText.size();
218     char *TextPtr = Allocator.Allocate<char>(Length + 1);
219 
220     memcpy(TextPtr, WordText.c_str(), Length + 1);
221     StringRef Text = StringRef(TextPtr, Length);
222 
223     formTokenWithChars(Tok, Loc, WordBegin,
224                        Pos.BufferPtr - WordBegin, Text);
225     return true;
226   }
227 
228   /// Put back tokens that we didn't consume.
229   void putBackLeftoverTokens() {
230     if (isEnd())
231       return;
232 
233     bool HavePartialTok = false;
234     Token PartialTok;
235     if (Pos.BufferPtr != Pos.BufferStart) {
236       formTokenWithChars(PartialTok, getSourceLocation(),
237                          Pos.BufferPtr, Pos.BufferEnd - Pos.BufferPtr,
238                          StringRef(Pos.BufferPtr,
239                                    Pos.BufferEnd - Pos.BufferPtr));
240       HavePartialTok = true;
241       Pos.CurToken++;
242     }
243 
244     P.putBack(llvm::makeArrayRef(Toks.begin() + Pos.CurToken, Toks.end()));
245     Pos.CurToken = Toks.size();
246 
247     if (HavePartialTok)
248       P.putBack(PartialTok);
249   }
250 };
251 
252 Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator,
253                const SourceManager &SourceMgr, DiagnosticsEngine &Diags,
254                const CommandTraits &Traits):
255     L(L), S(S), Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags),
256     Traits(Traits) {
257   consumeToken();
258 }
259 
260 void Parser::parseParamCommandArgs(ParamCommandComment *PC,
261                                    TextTokenRetokenizer &Retokenizer) {
262   Token Arg;
263   // Check if argument looks like direction specification: [dir]
264   // e.g., [in], [out], [in,out]
265   if (Retokenizer.lexDelimitedSeq(Arg, '[', ']'))
266     S.actOnParamCommandDirectionArg(PC,
267                                     Arg.getLocation(),
268                                     Arg.getEndLocation(),
269                                     Arg.getText());
270 
271   if (Retokenizer.lexWord(Arg))
272     S.actOnParamCommandParamNameArg(PC,
273                                     Arg.getLocation(),
274                                     Arg.getEndLocation(),
275                                     Arg.getText());
276 }
277 
278 void Parser::parseTParamCommandArgs(TParamCommandComment *TPC,
279                                     TextTokenRetokenizer &Retokenizer) {
280   Token Arg;
281   if (Retokenizer.lexWord(Arg))
282     S.actOnTParamCommandParamNameArg(TPC,
283                                      Arg.getLocation(),
284                                      Arg.getEndLocation(),
285                                      Arg.getText());
286 }
287 
288 void Parser::parseBlockCommandArgs(BlockCommandComment *BC,
289                                    TextTokenRetokenizer &Retokenizer,
290                                    unsigned NumArgs) {
291   typedef BlockCommandComment::Argument Argument;
292   Argument *Args =
293       new (Allocator.Allocate<Argument>(NumArgs)) Argument[NumArgs];
294   unsigned ParsedArgs = 0;
295   Token Arg;
296   while (ParsedArgs < NumArgs && Retokenizer.lexWord(Arg)) {
297     Args[ParsedArgs] = Argument(SourceRange(Arg.getLocation(),
298                                             Arg.getEndLocation()),
299                                 Arg.getText());
300     ParsedArgs++;
301   }
302 
303   S.actOnBlockCommandArgs(BC, llvm::makeArrayRef(Args, ParsedArgs));
304 }
305 
306 BlockCommandComment *Parser::parseBlockCommand() {
307   assert(Tok.is(tok::command));
308 
309   ParamCommandComment *PC;
310   TParamCommandComment *TPC;
311   BlockCommandComment *BC;
312   bool IsParam = false;
313   bool IsTParam = false;
314   const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
315   if (Info->IsParamCommand) {
316     IsParam = true;
317     PC = S.actOnParamCommandStart(Tok.getLocation(),
318                                   Tok.getEndLocation(),
319                                   Tok.getCommandID());
320   } else if (Info->IsTParamCommand) {
321     IsTParam = true;
322     TPC = S.actOnTParamCommandStart(Tok.getLocation(),
323                                     Tok.getEndLocation(),
324                                     Tok.getCommandID());
325   } else {
326     BC = S.actOnBlockCommandStart(Tok.getLocation(),
327                                   Tok.getEndLocation(),
328                                   Tok.getCommandID());
329   }
330   consumeToken();
331 
332   if (isTokBlockCommand()) {
333     // Block command ahead.  We can't nest block commands, so pretend that this
334     // command has an empty argument.
335     ParagraphComment *Paragraph = S.actOnParagraphComment(
336                                 ArrayRef<InlineContentComment *>());
337     if (IsParam) {
338       S.actOnParamCommandFinish(PC, Paragraph);
339       return PC;
340     } else if (IsTParam) {
341       S.actOnTParamCommandFinish(TPC, Paragraph);
342       return TPC;
343     } else {
344       S.actOnBlockCommandFinish(BC, Paragraph);
345       return BC;
346     }
347   }
348 
349   if (IsParam || IsTParam || Info->NumArgs > 0) {
350     // In order to parse command arguments we need to retokenize a few
351     // following text tokens.
352     TextTokenRetokenizer Retokenizer(Allocator, *this);
353 
354     if (IsParam)
355       parseParamCommandArgs(PC, Retokenizer);
356     else if (IsTParam)
357       parseTParamCommandArgs(TPC, Retokenizer);
358     else
359       parseBlockCommandArgs(BC, Retokenizer, Info->NumArgs);
360 
361     Retokenizer.putBackLeftoverTokens();
362   }
363 
364   // If there's a block command ahead, we will attach an empty paragraph to
365   // this command.
366   bool EmptyParagraph = false;
367   if (isTokBlockCommand())
368     EmptyParagraph = true;
369   else if (Tok.is(tok::newline)) {
370     Token PrevTok = Tok;
371     consumeToken();
372     EmptyParagraph = isTokBlockCommand();
373     putBack(PrevTok);
374   }
375 
376   ParagraphComment *Paragraph;
377   if (EmptyParagraph)
378     Paragraph = S.actOnParagraphComment(ArrayRef<InlineContentComment *>());
379   else {
380     BlockContentComment *Block = parseParagraphOrBlockCommand();
381     // Since we have checked for a block command, we should have parsed a
382     // paragraph.
383     Paragraph = cast<ParagraphComment>(Block);
384   }
385 
386   if (IsParam) {
387     S.actOnParamCommandFinish(PC, Paragraph);
388     return PC;
389   } else if (IsTParam) {
390     S.actOnTParamCommandFinish(TPC, Paragraph);
391     return TPC;
392   } else {
393     S.actOnBlockCommandFinish(BC, Paragraph);
394     return BC;
395   }
396 }
397 
398 InlineCommandComment *Parser::parseInlineCommand() {
399   assert(Tok.is(tok::command));
400 
401   const Token CommandTok = Tok;
402   consumeToken();
403 
404   TextTokenRetokenizer Retokenizer(Allocator, *this);
405 
406   Token ArgTok;
407   bool ArgTokValid = Retokenizer.lexWord(ArgTok);
408 
409   InlineCommandComment *IC;
410   if (ArgTokValid) {
411     IC = S.actOnInlineCommand(CommandTok.getLocation(),
412                               CommandTok.getEndLocation(),
413                               CommandTok.getCommandID(),
414                               ArgTok.getLocation(),
415                               ArgTok.getEndLocation(),
416                               ArgTok.getText());
417   } else {
418     IC = S.actOnInlineCommand(CommandTok.getLocation(),
419                               CommandTok.getEndLocation(),
420                               CommandTok.getCommandID());
421   }
422 
423   Retokenizer.putBackLeftoverTokens();
424 
425   return IC;
426 }
427 
428 HTMLStartTagComment *Parser::parseHTMLStartTag() {
429   assert(Tok.is(tok::html_start_tag));
430   HTMLStartTagComment *HST =
431       S.actOnHTMLStartTagStart(Tok.getLocation(),
432                                Tok.getHTMLTagStartName());
433   consumeToken();
434 
435   SmallVector<HTMLStartTagComment::Attribute, 2> Attrs;
436   while (true) {
437     switch (Tok.getKind()) {
438     case tok::html_ident: {
439       Token Ident = Tok;
440       consumeToken();
441       if (Tok.isNot(tok::html_equals)) {
442         Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),
443                                                        Ident.getHTMLIdent()));
444         continue;
445       }
446       Token Equals = Tok;
447       consumeToken();
448       if (Tok.isNot(tok::html_quoted_string)) {
449         Diag(Tok.getLocation(),
450              diag::warn_doc_html_start_tag_expected_quoted_string)
451           << SourceRange(Equals.getLocation());
452         Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),
453                                                        Ident.getHTMLIdent()));
454         while (Tok.is(tok::html_equals) ||
455                Tok.is(tok::html_quoted_string))
456           consumeToken();
457         continue;
458       }
459       Attrs.push_back(HTMLStartTagComment::Attribute(
460                               Ident.getLocation(),
461                               Ident.getHTMLIdent(),
462                               Equals.getLocation(),
463                               SourceRange(Tok.getLocation(),
464                                           Tok.getEndLocation()),
465                               Tok.getHTMLQuotedString()));
466       consumeToken();
467       continue;
468     }
469 
470     case tok::html_greater:
471       S.actOnHTMLStartTagFinish(HST,
472                                 S.copyArray(llvm::makeArrayRef(Attrs)),
473                                 Tok.getLocation(),
474                                 /* IsSelfClosing = */ false);
475       consumeToken();
476       return HST;
477 
478     case tok::html_slash_greater:
479       S.actOnHTMLStartTagFinish(HST,
480                                 S.copyArray(llvm::makeArrayRef(Attrs)),
481                                 Tok.getLocation(),
482                                 /* IsSelfClosing = */ true);
483       consumeToken();
484       return HST;
485 
486     case tok::html_equals:
487     case tok::html_quoted_string:
488       Diag(Tok.getLocation(),
489            diag::warn_doc_html_start_tag_expected_ident_or_greater);
490       while (Tok.is(tok::html_equals) ||
491              Tok.is(tok::html_quoted_string))
492         consumeToken();
493       if (Tok.is(tok::html_ident) ||
494           Tok.is(tok::html_greater) ||
495           Tok.is(tok::html_slash_greater))
496         continue;
497 
498       S.actOnHTMLStartTagFinish(HST,
499                                 S.copyArray(llvm::makeArrayRef(Attrs)),
500                                 SourceLocation(),
501                                 /* IsSelfClosing = */ false);
502       return HST;
503 
504     default:
505       // Not a token from an HTML start tag.  Thus HTML tag prematurely ended.
506       S.actOnHTMLStartTagFinish(HST,
507                                 S.copyArray(llvm::makeArrayRef(Attrs)),
508                                 SourceLocation(),
509                                 /* IsSelfClosing = */ false);
510       bool StartLineInvalid;
511       const unsigned StartLine = SourceMgr.getPresumedLineNumber(
512                                                   HST->getLocation(),
513                                                   &StartLineInvalid);
514       bool EndLineInvalid;
515       const unsigned EndLine = SourceMgr.getPresumedLineNumber(
516                                                   Tok.getLocation(),
517                                                   &EndLineInvalid);
518       if (StartLineInvalid || EndLineInvalid || StartLine == EndLine)
519         Diag(Tok.getLocation(),
520              diag::warn_doc_html_start_tag_expected_ident_or_greater)
521           << HST->getSourceRange();
522       else {
523         Diag(Tok.getLocation(),
524              diag::warn_doc_html_start_tag_expected_ident_or_greater);
525         Diag(HST->getLocation(), diag::note_doc_html_tag_started_here)
526           << HST->getSourceRange();
527       }
528       return HST;
529     }
530   }
531 }
532 
533 HTMLEndTagComment *Parser::parseHTMLEndTag() {
534   assert(Tok.is(tok::html_end_tag));
535   Token TokEndTag = Tok;
536   consumeToken();
537   SourceLocation Loc;
538   if (Tok.is(tok::html_greater)) {
539     Loc = Tok.getLocation();
540     consumeToken();
541   }
542 
543   return S.actOnHTMLEndTag(TokEndTag.getLocation(),
544                            Loc,
545                            TokEndTag.getHTMLTagEndName());
546 }
547 
548 BlockContentComment *Parser::parseParagraphOrBlockCommand() {
549   SmallVector<InlineContentComment *, 8> Content;
550 
551   while (true) {
552     switch (Tok.getKind()) {
553     case tok::verbatim_block_begin:
554     case tok::verbatim_line_name:
555     case tok::eof:
556       assert(Content.size() != 0);
557       break; // Block content or EOF ahead, finish this parapgaph.
558 
559     case tok::unknown_command:
560       Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
561                                               Tok.getEndLocation(),
562                                               Tok.getUnknownCommandName()));
563       consumeToken();
564       continue;
565 
566     case tok::command: {
567       const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
568       if (Info->IsBlockCommand) {
569         if (Content.size() == 0)
570           return parseBlockCommand();
571         break; // Block command ahead, finish this parapgaph.
572       }
573       if (Info->IsVerbatimBlockEndCommand) {
574         Diag(Tok.getLocation(),
575              diag::warn_verbatim_block_end_without_start)
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