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