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