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