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