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
17namespace clang {
18
19static 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
27namespace comments {
28
29/// Re-lexes a sequence of tok::text tokens.
30class 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
145public:
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
256Parser::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
264void 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
282void 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
292ArrayRef<Comment::Argument>
293Parser::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
307BlockCommandComment *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
399InlineCommandComment *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
427HTMLStartTagComment *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
528HTMLEndTagComment *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
543BlockContentComment *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
644VerbatimBlockComment *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
690VerbatimLineComment *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
716BlockContentComment *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
747FullComment *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