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