1 | //===--- CommentParser.cpp - Doxygen comment parser -----------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "clang/AST/CommentParser.h" |
10 | #include "clang/AST/CommentCommandTraits.h" |
11 | #include "clang/AST/CommentDiagnostic.h" |
12 | #include "clang/AST/CommentSema.h" |
13 | #include "clang/Basic/CharInfo.h" |
14 | #include "clang/Basic/SourceManager.h" |
15 | #include "llvm/Support/ErrorHandling.h" |
16 | |
17 | namespace clang { |
18 | |
19 | static inline bool isWhitespace(llvm::StringRef S) { |
20 | for (StringRef::const_iterator I = S.begin(), E = S.end(); I != E; ++I) { |
21 | if (!isWhitespace(c: *I)) |
22 | return false; |
23 | } |
24 | return true; |
25 | } |
26 | |
27 | namespace comments { |
28 | |
29 | /// Re-lexes a sequence of tok::text tokens. |
30 | class { |
31 | llvm::BumpPtrAllocator &; |
32 | Parser &; |
33 | |
34 | /// This flag is set when there are no more tokens we can fetch from lexer. |
35 | bool ; |
36 | |
37 | /// Token buffer: tokens we have processed and lookahead. |
38 | SmallVector<Token, 16> ; |
39 | |
40 | /// A position in \c Toks. |
41 | struct { |
42 | const char *; |
43 | const char *; |
44 | const char *; |
45 | SourceLocation ; |
46 | unsigned ; |
47 | }; |
48 | |
49 | /// Current position in Toks. |
50 | Position ; |
51 | |
52 | bool () const { |
53 | return Pos.CurToken >= Toks.size(); |
54 | } |
55 | |
56 | /// Sets up the buffer pointers to point to current token. |
57 | void () { |
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 () const { |
68 | const unsigned CharNo = Pos.BufferPtr - Pos.BufferStart; |
69 | return Pos.BufferStartLoc.getLocWithOffset(Offset: CharNo); |
70 | } |
71 | |
72 | char () const { |
73 | assert(!isEnd()); |
74 | assert(Pos.BufferPtr != Pos.BufferEnd); |
75 | return *Pos.BufferPtr; |
76 | } |
77 | |
78 | void () { |
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 () { |
96 | if (NoMoreInterestingTokens) |
97 | return false; |
98 | |
99 | if (P.Tok.is(K: 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(K: tok::text)) { |
104 | P.putBack(OldTok: Newline); |
105 | NoMoreInterestingTokens = true; |
106 | return false; |
107 | } |
108 | } |
109 | if (P.Tok.isNot(K: tok::text)) { |
110 | NoMoreInterestingTokens = true; |
111 | return false; |
112 | } |
113 | |
114 | Toks.push_back(Elt: P.Tok); |
115 | P.consumeToken(); |
116 | if (Toks.size() == 1) |
117 | setupBuffer(); |
118 | return true; |
119 | } |
120 | |
121 | void () { |
122 | while (!isEnd()) { |
123 | if (isWhitespace(c: peek())) |
124 | consumeChar(); |
125 | else |
126 | break; |
127 | } |
128 | } |
129 | |
130 | void (Token &Result, |
131 | SourceLocation Loc, |
132 | const char *TokBegin, |
133 | unsigned TokLength, |
134 | StringRef Text) { |
135 | Result.setLocation(Loc); |
136 | Result.setKind(tok::text); |
137 | Result.setLength(TokLength); |
138 | #ifndef NDEBUG |
139 | Result.TextPtr = "<UNSET>" ; |
140 | Result.IntVal = 7; |
141 | #endif |
142 | Result.setText(Text); |
143 | } |
144 | |
145 | public: |
146 | (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 (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: C)) { |
166 | WordText.push_back(Elt: 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>(Num: Length + 1); |
178 | |
179 | memcpy(dest: TextPtr, src: WordText.c_str(), n: Length + 1); |
180 | StringRef Text = StringRef(TextPtr, Length); |
181 | |
182 | formTokenWithChars(Result&: Tok, Loc, TokBegin: WordBegin, TokLength: Length, Text); |
183 | return true; |
184 | } |
185 | |
186 | bool (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(Elt: C); |
201 | consumeChar(); |
202 | } else |
203 | Error = true; |
204 | } |
205 | char C = '\0'; |
206 | while (!Error && !isEnd()) { |
207 | C = peek(); |
208 | WordText.push_back(Elt: 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>(Num: Length + 1); |
223 | |
224 | memcpy(dest: TextPtr, src: WordText.c_str(), n: Length + 1); |
225 | StringRef Text = StringRef(TextPtr, Length); |
226 | |
227 | formTokenWithChars(Result&: Tok, Loc, TokBegin: WordBegin, |
228 | TokLength: Pos.BufferPtr - WordBegin, Text); |
229 | return true; |
230 | } |
231 | |
232 | /// Put back tokens that we didn't consume. |
233 | void () { |
234 | if (isEnd()) |
235 | return; |
236 | |
237 | bool HavePartialTok = false; |
238 | Token PartialTok; |
239 | if (Pos.BufferPtr != Pos.BufferStart) { |
240 | formTokenWithChars(Result&: PartialTok, Loc: getSourceLocation(), |
241 | TokBegin: Pos.BufferPtr, TokLength: Pos.BufferEnd - Pos.BufferPtr, |
242 | Text: StringRef(Pos.BufferPtr, |
243 | Pos.BufferEnd - Pos.BufferPtr)); |
244 | HavePartialTok = true; |
245 | Pos.CurToken++; |
246 | } |
247 | |
248 | P.putBack(Toks: llvm::ArrayRef(Toks.begin() + Pos.CurToken, Toks.end())); |
249 | Pos.CurToken = Toks.size(); |
250 | |
251 | if (HavePartialTok) |
252 | P.putBack(OldTok: PartialTok); |
253 | } |
254 | }; |
255 | |
256 | Parser::(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator, |
257 | const SourceManager &SourceMgr, DiagnosticsEngine &Diags, |
258 | const CommandTraits &Traits): |
259 | L(L), S(S), Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags), |
260 | Traits(Traits) { |
261 | consumeToken(); |
262 | } |
263 | |
264 | void Parser::parseParamCommandArgs(ParamCommandComment *PC, |
265 | TextTokenRetokenizer &Retokenizer) { |
266 | Token Arg; |
267 | // Check if argument looks like direction specification: [dir] |
268 | // e.g., [in], [out], [in,out] |
269 | if (Retokenizer.lexDelimitedSeq(Tok&: Arg, OpenDelim: '[', CloseDelim: ']')) |
270 | S.actOnParamCommandDirectionArg(Command: PC, |
271 | ArgLocBegin: Arg.getLocation(), |
272 | ArgLocEnd: Arg.getEndLocation(), |
273 | Arg: Arg.getText()); |
274 | |
275 | if (Retokenizer.lexWord(Tok&: Arg)) |
276 | S.actOnParamCommandParamNameArg(Command: PC, |
277 | ArgLocBegin: Arg.getLocation(), |
278 | ArgLocEnd: Arg.getEndLocation(), |
279 | Arg: Arg.getText()); |
280 | } |
281 | |
282 | void Parser::parseTParamCommandArgs(TParamCommandComment *TPC, |
283 | TextTokenRetokenizer &Retokenizer) { |
284 | Token Arg; |
285 | if (Retokenizer.lexWord(Tok&: Arg)) |
286 | S.actOnTParamCommandParamNameArg(Command: TPC, |
287 | ArgLocBegin: Arg.getLocation(), |
288 | ArgLocEnd: Arg.getEndLocation(), |
289 | Arg: Arg.getText()); |
290 | } |
291 | |
292 | ArrayRef<Comment::Argument> |
293 | Parser::parseCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs) { |
294 | auto *Args = new (Allocator.Allocate<Comment::Argument>(Num: NumArgs)) |
295 | Comment::Argument[NumArgs]; |
296 | unsigned ParsedArgs = 0; |
297 | Token Arg; |
298 | while (ParsedArgs < NumArgs && Retokenizer.lexWord(Tok&: Arg)) { |
299 | Args[ParsedArgs] = Comment::Argument{ |
300 | .Range: SourceRange(Arg.getLocation(), Arg.getEndLocation()), .Text: Arg.getText()}; |
301 | ParsedArgs++; |
302 | } |
303 | |
304 | return llvm::ArrayRef(Args, ParsedArgs); |
305 | } |
306 | |
307 | BlockCommandComment *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(CommandID: Tok.getCommandID()); |
314 | CommandMarkerKind CommandMarker = |
315 | Tok.is(K: tok::backslash_command) ? CMK_Backslash : CMK_At; |
316 | if (Info->IsParamCommand) { |
317 | PC = S.actOnParamCommandStart(LocBegin: Tok.getLocation(), |
318 | LocEnd: Tok.getEndLocation(), |
319 | CommandID: Tok.getCommandID(), |
320 | CommandMarker); |
321 | } else if (Info->IsTParamCommand) { |
322 | TPC = S.actOnTParamCommandStart(LocBegin: Tok.getLocation(), |
323 | LocEnd: Tok.getEndLocation(), |
324 | CommandID: Tok.getCommandID(), |
325 | CommandMarker); |
326 | } else { |
327 | BC = S.actOnBlockCommandStart(LocBegin: Tok.getLocation(), |
328 | LocEnd: Tok.getEndLocation(), |
329 | CommandID: 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(Content: std::nullopt); |
338 | if (PC) { |
339 | S.actOnParamCommandFinish(Command: PC, Paragraph); |
340 | return PC; |
341 | } else if (TPC) { |
342 | S.actOnTParamCommandFinish(Command: TPC, Paragraph); |
343 | return TPC; |
344 | } else { |
345 | S.actOnBlockCommandFinish(Command: 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(Command: BC, Args: parseCommandArgs(Retokenizer, NumArgs: 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(K: tok::newline)) { |
371 | Token PrevTok = Tok; |
372 | consumeToken(); |
373 | EmptyParagraph = isTokBlockCommand(); |
374 | putBack(OldTok: PrevTok); |
375 | } |
376 | |
377 | ParagraphComment *Paragraph; |
378 | if (EmptyParagraph) |
379 | Paragraph = S.actOnParagraphComment(Content: 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>(Val: Block); |
385 | } |
386 | |
387 | if (PC) { |
388 | S.actOnParamCommandFinish(Command: PC, Paragraph); |
389 | return PC; |
390 | } else if (TPC) { |
391 | S.actOnTParamCommandFinish(Command: TPC, Paragraph); |
392 | return TPC; |
393 | } else { |
394 | S.actOnBlockCommandFinish(Command: BC, Paragraph); |
395 | return BC; |
396 | } |
397 | } |
398 | |
399 | InlineCommandComment *Parser::parseInlineCommand() { |
400 | assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command)); |
401 | const CommandInfo *Info = Traits.getCommandInfo(CommandID: Tok.getCommandID()); |
402 | |
403 | const Token CommandTok = Tok; |
404 | consumeToken(); |
405 | |
406 | TextTokenRetokenizer Retokenizer(Allocator, *this); |
407 | ArrayRef<Comment::Argument> Args = |
408 | parseCommandArgs(Retokenizer, NumArgs: Info->NumArgs); |
409 | |
410 | InlineCommandComment *IC = S.actOnInlineCommand( |
411 | CommandLocBegin: CommandTok.getLocation(), CommandLocEnd: CommandTok.getEndLocation(), |
412 | CommandID: CommandTok.getCommandID(), Args); |
413 | |
414 | if (Args.size() < Info->NumArgs) { |
415 | Diag(Loc: CommandTok.getEndLocation().getLocWithOffset(Offset: 1), |
416 | diag::DiagID: warn_doc_inline_command_not_enough_arguments) |
417 | << CommandTok.is(K: 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 | |
427 | HTMLStartTagComment *Parser::() { |
428 | assert(Tok.is(tok::html_start_tag)); |
429 | HTMLStartTagComment *HST = |
430 | S.actOnHTMLStartTagStart(LocBegin: Tok.getLocation(), |
431 | TagName: 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(K: tok::html_equals)) { |
441 | Attrs.push_back(Elt: HTMLStartTagComment::Attribute(Ident.getLocation(), |
442 | Ident.getHTMLIdent())); |
443 | continue; |
444 | } |
445 | Token Equals = Tok; |
446 | consumeToken(); |
447 | if (Tok.isNot(K: tok::html_quoted_string)) { |
448 | Diag(Loc: Tok.getLocation(), |
449 | diag::DiagID: warn_doc_html_start_tag_expected_quoted_string) |
450 | << SourceRange(Equals.getLocation()); |
451 | Attrs.push_back(Elt: HTMLStartTagComment::Attribute(Ident.getLocation(), |
452 | Ident.getHTMLIdent())); |
453 | while (Tok.is(K: tok::html_equals) || |
454 | Tok.is(K: tok::html_quoted_string)) |
455 | consumeToken(); |
456 | continue; |
457 | } |
458 | Attrs.push_back(Elt: 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(Tag: HST, Attrs: S.copyArray(Source: llvm::ArrayRef(Attrs)), |
471 | GreaterLoc: Tok.getLocation(), |
472 | /* IsSelfClosing = */ false); |
473 | consumeToken(); |
474 | return HST; |
475 | |
476 | case tok::html_slash_greater: |
477 | S.actOnHTMLStartTagFinish(Tag: HST, Attrs: S.copyArray(Source: llvm::ArrayRef(Attrs)), |
478 | GreaterLoc: Tok.getLocation(), |
479 | /* IsSelfClosing = */ true); |
480 | consumeToken(); |
481 | return HST; |
482 | |
483 | case tok::html_equals: |
484 | case tok::html_quoted_string: |
485 | Diag(Loc: Tok.getLocation(), |
486 | diag::DiagID: warn_doc_html_start_tag_expected_ident_or_greater); |
487 | while (Tok.is(K: tok::html_equals) || |
488 | Tok.is(K: tok::html_quoted_string)) |
489 | consumeToken(); |
490 | if (Tok.is(K: tok::html_ident) || |
491 | Tok.is(K: tok::html_greater) || |
492 | Tok.is(K: tok::html_slash_greater)) |
493 | continue; |
494 | |
495 | S.actOnHTMLStartTagFinish(Tag: HST, Attrs: S.copyArray(Source: llvm::ArrayRef(Attrs)), |
496 | GreaterLoc: 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(Tag: HST, Attrs: S.copyArray(Source: llvm::ArrayRef(Attrs)), |
503 | GreaterLoc: SourceLocation(), |
504 | /* IsSelfClosing = */ false); |
505 | bool StartLineInvalid; |
506 | const unsigned StartLine = SourceMgr.getPresumedLineNumber( |
507 | Loc: HST->getLocation(), |
508 | Invalid: &StartLineInvalid); |
509 | bool EndLineInvalid; |
510 | const unsigned EndLine = SourceMgr.getPresumedLineNumber( |
511 | Loc: Tok.getLocation(), |
512 | Invalid: &EndLineInvalid); |
513 | if (StartLineInvalid || EndLineInvalid || StartLine == EndLine) |
514 | Diag(Loc: Tok.getLocation(), |
515 | diag::DiagID: warn_doc_html_start_tag_expected_ident_or_greater) |
516 | << HST->getSourceRange(); |
517 | else { |
518 | Diag(Loc: Tok.getLocation(), |
519 | diag::DiagID: warn_doc_html_start_tag_expected_ident_or_greater); |
520 | Diag(Loc: HST->getLocation(), diag::DiagID: note_doc_html_tag_started_here) |
521 | << HST->getSourceRange(); |
522 | } |
523 | return HST; |
524 | } |
525 | } |
526 | } |
527 | |
528 | HTMLEndTagComment *Parser::() { |
529 | assert(Tok.is(tok::html_end_tag)); |
530 | Token TokEndTag = Tok; |
531 | consumeToken(); |
532 | SourceLocation Loc; |
533 | if (Tok.is(K: tok::html_greater)) { |
534 | Loc = Tok.getLocation(); |
535 | consumeToken(); |
536 | } |
537 | |
538 | return S.actOnHTMLEndTag(LocBegin: TokEndTag.getLocation(), |
539 | LocEnd: Loc, |
540 | TagName: TokEndTag.getHTMLTagEndName()); |
541 | } |
542 | |
543 | BlockContentComment *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(Elt: S.actOnUnknownCommand(LocBegin: Tok.getLocation(), |
555 | LocEnd: Tok.getEndLocation(), |
556 | CommandName: Tok.getUnknownCommandName())); |
557 | consumeToken(); |
558 | continue; |
559 | |
560 | case tok::backslash_command: |
561 | case tok::at_command: { |
562 | const CommandInfo *Info = Traits.getCommandInfo(CommandID: 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(Loc: Tok.getLocation(), |
570 | diag::DiagID: warn_verbatim_block_end_without_start) |
571 | << Tok.is(K: 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(Elt: S.actOnUnknownCommand(LocBegin: Tok.getLocation(), |
579 | LocEnd: Tok.getEndLocation(), |
580 | CommandID: 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(K: tok::newline) || Tok.is(K: 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(K: tok::text) && isWhitespace(S: Tok.getText())) { |
598 | Token WhitespaceTok = Tok; |
599 | consumeToken(); |
600 | if (Tok.is(K: tok::newline) || Tok.is(K: tok::eof)) { |
601 | consumeToken(); |
602 | break; |
603 | } |
604 | // We have [tok::newline, tok::text, non-newline]. Put back tok::text. |
605 | putBack(OldTok: 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(Elt: parseHTMLEndTag()); |
619 | continue; |
620 | |
621 | case tok::text: |
622 | Content.push_back(Elt: S.actOnText(LocBegin: Tok.getLocation(), |
623 | LocEnd: Tok.getEndLocation(), |
624 | Text: 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(Content: S.copyArray(Source: llvm::ArrayRef(Content))); |
642 | } |
643 | |
644 | VerbatimBlockComment *Parser::() { |
645 | assert(Tok.is(tok::verbatim_block_begin)); |
646 | |
647 | VerbatimBlockComment *VB = |
648 | S.actOnVerbatimBlockStart(Loc: Tok.getLocation(), |
649 | CommandID: 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(K: tok::newline)) |
655 | consumeToken(); |
656 | |
657 | SmallVector<VerbatimBlockLineComment *, 8> Lines; |
658 | while (Tok.is(K: tok::verbatim_block_line) || |
659 | Tok.is(K: tok::newline)) { |
660 | VerbatimBlockLineComment *Line; |
661 | if (Tok.is(K: tok::verbatim_block_line)) { |
662 | Line = S.actOnVerbatimBlockLine(Loc: Tok.getLocation(), |
663 | Text: Tok.getVerbatimBlockText()); |
664 | consumeToken(); |
665 | if (Tok.is(K: tok::newline)) { |
666 | consumeToken(); |
667 | } |
668 | } else { |
669 | // Empty line, just a tok::newline. |
670 | Line = S.actOnVerbatimBlockLine(Loc: Tok.getLocation(), Text: "" ); |
671 | consumeToken(); |
672 | } |
673 | Lines.push_back(Elt: Line); |
674 | } |
675 | |
676 | if (Tok.is(K: tok::verbatim_block_end)) { |
677 | const CommandInfo *Info = Traits.getCommandInfo(CommandID: Tok.getVerbatimBlockID()); |
678 | S.actOnVerbatimBlockFinish(Block: VB, CloseNameLocBegin: Tok.getLocation(), CloseName: Info->Name, |
679 | Lines: S.copyArray(Source: llvm::ArrayRef(Lines))); |
680 | consumeToken(); |
681 | } else { |
682 | // Unterminated \\verbatim block |
683 | S.actOnVerbatimBlockFinish(Block: VB, CloseNameLocBegin: SourceLocation(), CloseName: "" , |
684 | Lines: S.copyArray(Source: llvm::ArrayRef(Lines))); |
685 | } |
686 | |
687 | return VB; |
688 | } |
689 | |
690 | VerbatimLineComment *Parser::() { |
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(K: 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(LocBegin: NameTok.getLocation(), |
709 | CommandID: NameTok.getVerbatimLineID(), |
710 | TextBegin, |
711 | Text); |
712 | consumeToken(); |
713 | return VL; |
714 | } |
715 | |
716 | BlockContentComment *Parser::() { |
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 | |
747 | FullComment *Parser::() { |
748 | // Skip newlines at the beginning of the comment. |
749 | while (Tok.is(K: tok::newline)) |
750 | consumeToken(); |
751 | |
752 | SmallVector<BlockContentComment *, 8> Blocks; |
753 | while (Tok.isNot(K: tok::eof)) { |
754 | Blocks.push_back(Elt: parseBlockContent()); |
755 | |
756 | // Skip extra newlines after paragraph end. |
757 | while (Tok.is(K: tok::newline)) |
758 | consumeToken(); |
759 | } |
760 | return S.actOnFullComment(Blocks: S.copyArray(Source: llvm::ArrayRef(Blocks))); |
761 | } |
762 | |
763 | } // end namespace comments |
764 | } // end namespace clang |
765 | |