781 lines
24 KiB
C++
781 lines
24 KiB
C++
//===--- CommentParser.cpp - Doxygen comment parser -----------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/AST/CommentParser.h"
|
|
#include "clang/AST/CommentCommandTraits.h"
|
|
#include "clang/AST/CommentDiagnostic.h"
|
|
#include "clang/AST/CommentSema.h"
|
|
#include "clang/Basic/CharInfo.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
|
|
namespace clang {
|
|
|
|
static inline bool isWhitespace(llvm::StringRef S) {
|
|
for (StringRef::const_iterator I = S.begin(), E = S.end(); I != E; ++I) {
|
|
if (!isWhitespace(*I))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
namespace comments {
|
|
|
|
/// Re-lexes a sequence of tok::text tokens.
|
|
class TextTokenRetokenizer {
|
|
llvm::BumpPtrAllocator &Allocator;
|
|
Parser &P;
|
|
|
|
/// This flag is set when there are no more tokens we can fetch from lexer.
|
|
bool NoMoreInterestingTokens;
|
|
|
|
/// Token buffer: tokens we have processed and lookahead.
|
|
SmallVector<Token, 16> Toks;
|
|
|
|
/// A position in \c Toks.
|
|
struct Position {
|
|
const char *BufferStart;
|
|
const char *BufferEnd;
|
|
const char *BufferPtr;
|
|
SourceLocation BufferStartLoc;
|
|
unsigned CurToken;
|
|
};
|
|
|
|
/// Current position in Toks.
|
|
Position Pos;
|
|
|
|
bool isEnd() const {
|
|
return Pos.CurToken >= Toks.size();
|
|
}
|
|
|
|
/// Sets up the buffer pointers to point to current token.
|
|
void setupBuffer() {
|
|
assert(!isEnd());
|
|
const Token &Tok = Toks[Pos.CurToken];
|
|
|
|
Pos.BufferStart = Tok.getText().begin();
|
|
Pos.BufferEnd = Tok.getText().end();
|
|
Pos.BufferPtr = Pos.BufferStart;
|
|
Pos.BufferStartLoc = Tok.getLocation();
|
|
}
|
|
|
|
SourceLocation getSourceLocation() const {
|
|
const unsigned CharNo = Pos.BufferPtr - Pos.BufferStart;
|
|
return Pos.BufferStartLoc.getLocWithOffset(CharNo);
|
|
}
|
|
|
|
char peek() const {
|
|
assert(!isEnd());
|
|
assert(Pos.BufferPtr != Pos.BufferEnd);
|
|
return *Pos.BufferPtr;
|
|
}
|
|
|
|
void consumeChar() {
|
|
assert(!isEnd());
|
|
assert(Pos.BufferPtr != Pos.BufferEnd);
|
|
Pos.BufferPtr++;
|
|
if (Pos.BufferPtr == Pos.BufferEnd) {
|
|
Pos.CurToken++;
|
|
if (isEnd() && !addToken())
|
|
return;
|
|
|
|
assert(!isEnd());
|
|
setupBuffer();
|
|
}
|
|
}
|
|
|
|
/// Add a token.
|
|
/// Returns true on success, false if there are no interesting tokens to
|
|
/// fetch from lexer.
|
|
bool addToken() {
|
|
if (NoMoreInterestingTokens)
|
|
return false;
|
|
|
|
if (P.Tok.is(tok::newline)) {
|
|
// If we see a single newline token between text tokens, skip it.
|
|
Token Newline = P.Tok;
|
|
P.consumeToken();
|
|
if (P.Tok.isNot(tok::text)) {
|
|
P.putBack(Newline);
|
|
NoMoreInterestingTokens = true;
|
|
return false;
|
|
}
|
|
}
|
|
if (P.Tok.isNot(tok::text)) {
|
|
NoMoreInterestingTokens = true;
|
|
return false;
|
|
}
|
|
|
|
Toks.push_back(P.Tok);
|
|
P.consumeToken();
|
|
if (Toks.size() == 1)
|
|
setupBuffer();
|
|
return true;
|
|
}
|
|
|
|
void consumeWhitespace() {
|
|
while (!isEnd()) {
|
|
if (isWhitespace(peek()))
|
|
consumeChar();
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
void formTokenWithChars(Token &Result,
|
|
SourceLocation Loc,
|
|
const char *TokBegin,
|
|
unsigned TokLength,
|
|
StringRef Text) {
|
|
Result.setLocation(Loc);
|
|
Result.setKind(tok::text);
|
|
Result.setLength(TokLength);
|
|
#ifndef NDEBUG
|
|
Result.TextPtr = "<UNSET>";
|
|
Result.IntVal = 7;
|
|
#endif
|
|
Result.setText(Text);
|
|
}
|
|
|
|
public:
|
|
TextTokenRetokenizer(llvm::BumpPtrAllocator &Allocator, Parser &P):
|
|
Allocator(Allocator), P(P), NoMoreInterestingTokens(false) {
|
|
Pos.CurToken = 0;
|
|
addToken();
|
|
}
|
|
|
|
/// Extract a word -- sequence of non-whitespace characters.
|
|
bool lexWord(Token &Tok) {
|
|
if (isEnd())
|
|
return false;
|
|
|
|
Position SavedPos = Pos;
|
|
|
|
consumeWhitespace();
|
|
SmallString<32> WordText;
|
|
const char *WordBegin = Pos.BufferPtr;
|
|
SourceLocation Loc = getSourceLocation();
|
|
while (!isEnd()) {
|
|
const char C = peek();
|
|
if (!isWhitespace(C)) {
|
|
WordText.push_back(C);
|
|
consumeChar();
|
|
} else
|
|
break;
|
|
}
|
|
const unsigned Length = WordText.size();
|
|
if (Length == 0) {
|
|
Pos = SavedPos;
|
|
return false;
|
|
}
|
|
|
|
char *TextPtr = Allocator.Allocate<char>(Length + 1);
|
|
|
|
memcpy(TextPtr, WordText.c_str(), Length + 1);
|
|
StringRef Text = StringRef(TextPtr, Length);
|
|
|
|
formTokenWithChars(Tok, Loc, WordBegin, Length, Text);
|
|
return true;
|
|
}
|
|
|
|
bool lexDelimitedSeq(Token &Tok, char OpenDelim, char CloseDelim) {
|
|
if (isEnd())
|
|
return false;
|
|
|
|
Position SavedPos = Pos;
|
|
|
|
consumeWhitespace();
|
|
SmallString<32> WordText;
|
|
const char *WordBegin = Pos.BufferPtr;
|
|
SourceLocation Loc = getSourceLocation();
|
|
bool Error = false;
|
|
if (!isEnd()) {
|
|
const char C = peek();
|
|
if (C == OpenDelim) {
|
|
WordText.push_back(C);
|
|
consumeChar();
|
|
} else
|
|
Error = true;
|
|
}
|
|
char C = '\0';
|
|
while (!Error && !isEnd()) {
|
|
C = peek();
|
|
WordText.push_back(C);
|
|
consumeChar();
|
|
if (C == CloseDelim)
|
|
break;
|
|
}
|
|
if (!Error && C != CloseDelim)
|
|
Error = true;
|
|
|
|
if (Error) {
|
|
Pos = SavedPos;
|
|
return false;
|
|
}
|
|
|
|
const unsigned Length = WordText.size();
|
|
char *TextPtr = Allocator.Allocate<char>(Length + 1);
|
|
|
|
memcpy(TextPtr, WordText.c_str(), Length + 1);
|
|
StringRef Text = StringRef(TextPtr, Length);
|
|
|
|
formTokenWithChars(Tok, Loc, WordBegin,
|
|
Pos.BufferPtr - WordBegin, Text);
|
|
return true;
|
|
}
|
|
|
|
/// Put back tokens that we didn't consume.
|
|
void putBackLeftoverTokens() {
|
|
if (isEnd())
|
|
return;
|
|
|
|
bool HavePartialTok = false;
|
|
Token PartialTok;
|
|
if (Pos.BufferPtr != Pos.BufferStart) {
|
|
formTokenWithChars(PartialTok, getSourceLocation(),
|
|
Pos.BufferPtr, Pos.BufferEnd - Pos.BufferPtr,
|
|
StringRef(Pos.BufferPtr,
|
|
Pos.BufferEnd - Pos.BufferPtr));
|
|
HavePartialTok = true;
|
|
Pos.CurToken++;
|
|
}
|
|
|
|
P.putBack(llvm::makeArrayRef(Toks.begin() + Pos.CurToken, Toks.end()));
|
|
Pos.CurToken = Toks.size();
|
|
|
|
if (HavePartialTok)
|
|
P.putBack(PartialTok);
|
|
}
|
|
};
|
|
|
|
Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator,
|
|
const SourceManager &SourceMgr, DiagnosticsEngine &Diags,
|
|
const CommandTraits &Traits):
|
|
L(L), S(S), Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags),
|
|
Traits(Traits) {
|
|
consumeToken();
|
|
}
|
|
|
|
void Parser::parseParamCommandArgs(ParamCommandComment *PC,
|
|
TextTokenRetokenizer &Retokenizer) {
|
|
Token Arg;
|
|
// Check if argument looks like direction specification: [dir]
|
|
// e.g., [in], [out], [in,out]
|
|
if (Retokenizer.lexDelimitedSeq(Arg, '[', ']'))
|
|
S.actOnParamCommandDirectionArg(PC,
|
|
Arg.getLocation(),
|
|
Arg.getEndLocation(),
|
|
Arg.getText());
|
|
|
|
if (Retokenizer.lexWord(Arg))
|
|
S.actOnParamCommandParamNameArg(PC,
|
|
Arg.getLocation(),
|
|
Arg.getEndLocation(),
|
|
Arg.getText());
|
|
}
|
|
|
|
void Parser::parseTParamCommandArgs(TParamCommandComment *TPC,
|
|
TextTokenRetokenizer &Retokenizer) {
|
|
Token Arg;
|
|
if (Retokenizer.lexWord(Arg))
|
|
S.actOnTParamCommandParamNameArg(TPC,
|
|
Arg.getLocation(),
|
|
Arg.getEndLocation(),
|
|
Arg.getText());
|
|
}
|
|
|
|
void Parser::parseBlockCommandArgs(BlockCommandComment *BC,
|
|
TextTokenRetokenizer &Retokenizer,
|
|
unsigned NumArgs) {
|
|
typedef BlockCommandComment::Argument Argument;
|
|
Argument *Args =
|
|
new (Allocator.Allocate<Argument>(NumArgs)) Argument[NumArgs];
|
|
unsigned ParsedArgs = 0;
|
|
Token Arg;
|
|
while (ParsedArgs < NumArgs && Retokenizer.lexWord(Arg)) {
|
|
Args[ParsedArgs] = Argument(SourceRange(Arg.getLocation(),
|
|
Arg.getEndLocation()),
|
|
Arg.getText());
|
|
ParsedArgs++;
|
|
}
|
|
|
|
S.actOnBlockCommandArgs(BC, llvm::makeArrayRef(Args, ParsedArgs));
|
|
}
|
|
|
|
BlockCommandComment *Parser::parseBlockCommand() {
|
|
assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));
|
|
|
|
ParamCommandComment *PC = nullptr;
|
|
TParamCommandComment *TPC = nullptr;
|
|
BlockCommandComment *BC = nullptr;
|
|
const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
|
|
CommandMarkerKind CommandMarker =
|
|
Tok.is(tok::backslash_command) ? CMK_Backslash : CMK_At;
|
|
if (Info->IsParamCommand) {
|
|
PC = S.actOnParamCommandStart(Tok.getLocation(),
|
|
Tok.getEndLocation(),
|
|
Tok.getCommandID(),
|
|
CommandMarker);
|
|
} else if (Info->IsTParamCommand) {
|
|
TPC = S.actOnTParamCommandStart(Tok.getLocation(),
|
|
Tok.getEndLocation(),
|
|
Tok.getCommandID(),
|
|
CommandMarker);
|
|
} else {
|
|
BC = S.actOnBlockCommandStart(Tok.getLocation(),
|
|
Tok.getEndLocation(),
|
|
Tok.getCommandID(),
|
|
CommandMarker);
|
|
}
|
|
consumeToken();
|
|
|
|
if (isTokBlockCommand()) {
|
|
// Block command ahead. We can't nest block commands, so pretend that this
|
|
// command has an empty argument.
|
|
ParagraphComment *Paragraph = S.actOnParagraphComment(None);
|
|
if (PC) {
|
|
S.actOnParamCommandFinish(PC, Paragraph);
|
|
return PC;
|
|
} else if (TPC) {
|
|
S.actOnTParamCommandFinish(TPC, Paragraph);
|
|
return TPC;
|
|
} else {
|
|
S.actOnBlockCommandFinish(BC, Paragraph);
|
|
return BC;
|
|
}
|
|
}
|
|
|
|
if (PC || TPC || Info->NumArgs > 0) {
|
|
// In order to parse command arguments we need to retokenize a few
|
|
// following text tokens.
|
|
TextTokenRetokenizer Retokenizer(Allocator, *this);
|
|
|
|
if (PC)
|
|
parseParamCommandArgs(PC, Retokenizer);
|
|
else if (TPC)
|
|
parseTParamCommandArgs(TPC, Retokenizer);
|
|
else
|
|
parseBlockCommandArgs(BC, Retokenizer, Info->NumArgs);
|
|
|
|
Retokenizer.putBackLeftoverTokens();
|
|
}
|
|
|
|
// If there's a block command ahead, we will attach an empty paragraph to
|
|
// this command.
|
|
bool EmptyParagraph = false;
|
|
if (isTokBlockCommand())
|
|
EmptyParagraph = true;
|
|
else if (Tok.is(tok::newline)) {
|
|
Token PrevTok = Tok;
|
|
consumeToken();
|
|
EmptyParagraph = isTokBlockCommand();
|
|
putBack(PrevTok);
|
|
}
|
|
|
|
ParagraphComment *Paragraph;
|
|
if (EmptyParagraph)
|
|
Paragraph = S.actOnParagraphComment(None);
|
|
else {
|
|
BlockContentComment *Block = parseParagraphOrBlockCommand();
|
|
// Since we have checked for a block command, we should have parsed a
|
|
// paragraph.
|
|
Paragraph = cast<ParagraphComment>(Block);
|
|
}
|
|
|
|
if (PC) {
|
|
S.actOnParamCommandFinish(PC, Paragraph);
|
|
return PC;
|
|
} else if (TPC) {
|
|
S.actOnTParamCommandFinish(TPC, Paragraph);
|
|
return TPC;
|
|
} else {
|
|
S.actOnBlockCommandFinish(BC, Paragraph);
|
|
return BC;
|
|
}
|
|
}
|
|
|
|
InlineCommandComment *Parser::parseInlineCommand() {
|
|
assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));
|
|
|
|
const Token CommandTok = Tok;
|
|
consumeToken();
|
|
|
|
TextTokenRetokenizer Retokenizer(Allocator, *this);
|
|
|
|
Token ArgTok;
|
|
bool ArgTokValid = Retokenizer.lexWord(ArgTok);
|
|
|
|
InlineCommandComment *IC;
|
|
if (ArgTokValid) {
|
|
IC = S.actOnInlineCommand(CommandTok.getLocation(),
|
|
CommandTok.getEndLocation(),
|
|
CommandTok.getCommandID(),
|
|
ArgTok.getLocation(),
|
|
ArgTok.getEndLocation(),
|
|
ArgTok.getText());
|
|
} else {
|
|
IC = S.actOnInlineCommand(CommandTok.getLocation(),
|
|
CommandTok.getEndLocation(),
|
|
CommandTok.getCommandID());
|
|
|
|
Diag(CommandTok.getEndLocation().getLocWithOffset(1),
|
|
diag::warn_doc_inline_contents_no_argument)
|
|
<< CommandTok.is(tok::at_command)
|
|
<< Traits.getCommandInfo(CommandTok.getCommandID())->Name
|
|
<< SourceRange(CommandTok.getLocation(), CommandTok.getEndLocation());
|
|
}
|
|
|
|
Retokenizer.putBackLeftoverTokens();
|
|
|
|
return IC;
|
|
}
|
|
|
|
HTMLStartTagComment *Parser::parseHTMLStartTag() {
|
|
assert(Tok.is(tok::html_start_tag));
|
|
HTMLStartTagComment *HST =
|
|
S.actOnHTMLStartTagStart(Tok.getLocation(),
|
|
Tok.getHTMLTagStartName());
|
|
consumeToken();
|
|
|
|
SmallVector<HTMLStartTagComment::Attribute, 2> Attrs;
|
|
while (true) {
|
|
switch (Tok.getKind()) {
|
|
case tok::html_ident: {
|
|
Token Ident = Tok;
|
|
consumeToken();
|
|
if (Tok.isNot(tok::html_equals)) {
|
|
Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),
|
|
Ident.getHTMLIdent()));
|
|
continue;
|
|
}
|
|
Token Equals = Tok;
|
|
consumeToken();
|
|
if (Tok.isNot(tok::html_quoted_string)) {
|
|
Diag(Tok.getLocation(),
|
|
diag::warn_doc_html_start_tag_expected_quoted_string)
|
|
<< SourceRange(Equals.getLocation());
|
|
Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),
|
|
Ident.getHTMLIdent()));
|
|
while (Tok.is(tok::html_equals) ||
|
|
Tok.is(tok::html_quoted_string))
|
|
consumeToken();
|
|
continue;
|
|
}
|
|
Attrs.push_back(HTMLStartTagComment::Attribute(
|
|
Ident.getLocation(),
|
|
Ident.getHTMLIdent(),
|
|
Equals.getLocation(),
|
|
SourceRange(Tok.getLocation(),
|
|
Tok.getEndLocation()),
|
|
Tok.getHTMLQuotedString()));
|
|
consumeToken();
|
|
continue;
|
|
}
|
|
|
|
case tok::html_greater:
|
|
S.actOnHTMLStartTagFinish(HST,
|
|
S.copyArray(llvm::makeArrayRef(Attrs)),
|
|
Tok.getLocation(),
|
|
/* IsSelfClosing = */ false);
|
|
consumeToken();
|
|
return HST;
|
|
|
|
case tok::html_slash_greater:
|
|
S.actOnHTMLStartTagFinish(HST,
|
|
S.copyArray(llvm::makeArrayRef(Attrs)),
|
|
Tok.getLocation(),
|
|
/* IsSelfClosing = */ true);
|
|
consumeToken();
|
|
return HST;
|
|
|
|
case tok::html_equals:
|
|
case tok::html_quoted_string:
|
|
Diag(Tok.getLocation(),
|
|
diag::warn_doc_html_start_tag_expected_ident_or_greater);
|
|
while (Tok.is(tok::html_equals) ||
|
|
Tok.is(tok::html_quoted_string))
|
|
consumeToken();
|
|
if (Tok.is(tok::html_ident) ||
|
|
Tok.is(tok::html_greater) ||
|
|
Tok.is(tok::html_slash_greater))
|
|
continue;
|
|
|
|
S.actOnHTMLStartTagFinish(HST,
|
|
S.copyArray(llvm::makeArrayRef(Attrs)),
|
|
SourceLocation(),
|
|
/* IsSelfClosing = */ false);
|
|
return HST;
|
|
|
|
default:
|
|
// Not a token from an HTML start tag. Thus HTML tag prematurely ended.
|
|
S.actOnHTMLStartTagFinish(HST,
|
|
S.copyArray(llvm::makeArrayRef(Attrs)),
|
|
SourceLocation(),
|
|
/* IsSelfClosing = */ false);
|
|
bool StartLineInvalid;
|
|
const unsigned StartLine = SourceMgr.getPresumedLineNumber(
|
|
HST->getLocation(),
|
|
&StartLineInvalid);
|
|
bool EndLineInvalid;
|
|
const unsigned EndLine = SourceMgr.getPresumedLineNumber(
|
|
Tok.getLocation(),
|
|
&EndLineInvalid);
|
|
if (StartLineInvalid || EndLineInvalid || StartLine == EndLine)
|
|
Diag(Tok.getLocation(),
|
|
diag::warn_doc_html_start_tag_expected_ident_or_greater)
|
|
<< HST->getSourceRange();
|
|
else {
|
|
Diag(Tok.getLocation(),
|
|
diag::warn_doc_html_start_tag_expected_ident_or_greater);
|
|
Diag(HST->getLocation(), diag::note_doc_html_tag_started_here)
|
|
<< HST->getSourceRange();
|
|
}
|
|
return HST;
|
|
}
|
|
}
|
|
}
|
|
|
|
HTMLEndTagComment *Parser::parseHTMLEndTag() {
|
|
assert(Tok.is(tok::html_end_tag));
|
|
Token TokEndTag = Tok;
|
|
consumeToken();
|
|
SourceLocation Loc;
|
|
if (Tok.is(tok::html_greater)) {
|
|
Loc = Tok.getLocation();
|
|
consumeToken();
|
|
}
|
|
|
|
return S.actOnHTMLEndTag(TokEndTag.getLocation(),
|
|
Loc,
|
|
TokEndTag.getHTMLTagEndName());
|
|
}
|
|
|
|
BlockContentComment *Parser::parseParagraphOrBlockCommand() {
|
|
SmallVector<InlineContentComment *, 8> Content;
|
|
|
|
while (true) {
|
|
switch (Tok.getKind()) {
|
|
case tok::verbatim_block_begin:
|
|
case tok::verbatim_line_name:
|
|
case tok::eof:
|
|
break; // Block content or EOF ahead, finish this parapgaph.
|
|
|
|
case tok::unknown_command:
|
|
Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
|
|
Tok.getEndLocation(),
|
|
Tok.getUnknownCommandName()));
|
|
consumeToken();
|
|
continue;
|
|
|
|
case tok::backslash_command:
|
|
case tok::at_command: {
|
|
const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());
|
|
if (Info->IsBlockCommand) {
|
|
if (Content.size() == 0)
|
|
return parseBlockCommand();
|
|
break; // Block command ahead, finish this parapgaph.
|
|
}
|
|
if (Info->IsVerbatimBlockEndCommand) {
|
|
Diag(Tok.getLocation(),
|
|
diag::warn_verbatim_block_end_without_start)
|
|
<< Tok.is(tok::at_command)
|
|
<< Info->Name
|
|
<< SourceRange(Tok.getLocation(), Tok.getEndLocation());
|
|
consumeToken();
|
|
continue;
|
|
}
|
|
if (Info->IsUnknownCommand) {
|
|
Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
|
|
Tok.getEndLocation(),
|
|
Info->getID()));
|
|
consumeToken();
|
|
continue;
|
|
}
|
|
assert(Info->IsInlineCommand);
|
|
Content.push_back(parseInlineCommand());
|
|
continue;
|
|
}
|
|
|
|
case tok::newline: {
|
|
consumeToken();
|
|
if (Tok.is(tok::newline) || Tok.is(tok::eof)) {
|
|
consumeToken();
|
|
break; // Two newlines -- end of paragraph.
|
|
}
|
|
// Also allow [tok::newline, tok::text, tok::newline] if the middle
|
|
// tok::text is just whitespace.
|
|
if (Tok.is(tok::text) && isWhitespace(Tok.getText())) {
|
|
Token WhitespaceTok = Tok;
|
|
consumeToken();
|
|
if (Tok.is(tok::newline) || Tok.is(tok::eof)) {
|
|
consumeToken();
|
|
break;
|
|
}
|
|
// We have [tok::newline, tok::text, non-newline]. Put back tok::text.
|
|
putBack(WhitespaceTok);
|
|
}
|
|
if (Content.size() > 0)
|
|
Content.back()->addTrailingNewline();
|
|
continue;
|
|
}
|
|
|
|
// Don't deal with HTML tag soup now.
|
|
case tok::html_start_tag:
|
|
Content.push_back(parseHTMLStartTag());
|
|
continue;
|
|
|
|
case tok::html_end_tag:
|
|
Content.push_back(parseHTMLEndTag());
|
|
continue;
|
|
|
|
case tok::text:
|
|
Content.push_back(S.actOnText(Tok.getLocation(),
|
|
Tok.getEndLocation(),
|
|
Tok.getText()));
|
|
consumeToken();
|
|
continue;
|
|
|
|
case tok::verbatim_block_line:
|
|
case tok::verbatim_block_end:
|
|
case tok::verbatim_line_text:
|
|
case tok::html_ident:
|
|
case tok::html_equals:
|
|
case tok::html_quoted_string:
|
|
case tok::html_greater:
|
|
case tok::html_slash_greater:
|
|
llvm_unreachable("should not see this token");
|
|
}
|
|
break;
|
|
}
|
|
|
|
return S.actOnParagraphComment(S.copyArray(llvm::makeArrayRef(Content)));
|
|
}
|
|
|
|
VerbatimBlockComment *Parser::parseVerbatimBlock() {
|
|
assert(Tok.is(tok::verbatim_block_begin));
|
|
|
|
VerbatimBlockComment *VB =
|
|
S.actOnVerbatimBlockStart(Tok.getLocation(),
|
|
Tok.getVerbatimBlockID());
|
|
consumeToken();
|
|
|
|
// Don't create an empty line if verbatim opening command is followed
|
|
// by a newline.
|
|
if (Tok.is(tok::newline))
|
|
consumeToken();
|
|
|
|
SmallVector<VerbatimBlockLineComment *, 8> Lines;
|
|
while (Tok.is(tok::verbatim_block_line) ||
|
|
Tok.is(tok::newline)) {
|
|
VerbatimBlockLineComment *Line;
|
|
if (Tok.is(tok::verbatim_block_line)) {
|
|
Line = S.actOnVerbatimBlockLine(Tok.getLocation(),
|
|
Tok.getVerbatimBlockText());
|
|
consumeToken();
|
|
if (Tok.is(tok::newline)) {
|
|
consumeToken();
|
|
}
|
|
} else {
|
|
// Empty line, just a tok::newline.
|
|
Line = S.actOnVerbatimBlockLine(Tok.getLocation(), "");
|
|
consumeToken();
|
|
}
|
|
Lines.push_back(Line);
|
|
}
|
|
|
|
if (Tok.is(tok::verbatim_block_end)) {
|
|
const CommandInfo *Info = Traits.getCommandInfo(Tok.getVerbatimBlockID());
|
|
S.actOnVerbatimBlockFinish(VB, Tok.getLocation(),
|
|
Info->Name,
|
|
S.copyArray(llvm::makeArrayRef(Lines)));
|
|
consumeToken();
|
|
} else {
|
|
// Unterminated \\verbatim block
|
|
S.actOnVerbatimBlockFinish(VB, SourceLocation(), "",
|
|
S.copyArray(llvm::makeArrayRef(Lines)));
|
|
}
|
|
|
|
return VB;
|
|
}
|
|
|
|
VerbatimLineComment *Parser::parseVerbatimLine() {
|
|
assert(Tok.is(tok::verbatim_line_name));
|
|
|
|
Token NameTok = Tok;
|
|
consumeToken();
|
|
|
|
SourceLocation TextBegin;
|
|
StringRef Text;
|
|
// Next token might not be a tok::verbatim_line_text if verbatim line
|
|
// starting command comes just before a newline or comment end.
|
|
if (Tok.is(tok::verbatim_line_text)) {
|
|
TextBegin = Tok.getLocation();
|
|
Text = Tok.getVerbatimLineText();
|
|
} else {
|
|
TextBegin = NameTok.getEndLocation();
|
|
Text = "";
|
|
}
|
|
|
|
VerbatimLineComment *VL = S.actOnVerbatimLine(NameTok.getLocation(),
|
|
NameTok.getVerbatimLineID(),
|
|
TextBegin,
|
|
Text);
|
|
consumeToken();
|
|
return VL;
|
|
}
|
|
|
|
BlockContentComment *Parser::parseBlockContent() {
|
|
switch (Tok.getKind()) {
|
|
case tok::text:
|
|
case tok::unknown_command:
|
|
case tok::backslash_command:
|
|
case tok::at_command:
|
|
case tok::html_start_tag:
|
|
case tok::html_end_tag:
|
|
return parseParagraphOrBlockCommand();
|
|
|
|
case tok::verbatim_block_begin:
|
|
return parseVerbatimBlock();
|
|
|
|
case tok::verbatim_line_name:
|
|
return parseVerbatimLine();
|
|
|
|
case tok::eof:
|
|
case tok::newline:
|
|
case tok::verbatim_block_line:
|
|
case tok::verbatim_block_end:
|
|
case tok::verbatim_line_text:
|
|
case tok::html_ident:
|
|
case tok::html_equals:
|
|
case tok::html_quoted_string:
|
|
case tok::html_greater:
|
|
case tok::html_slash_greater:
|
|
llvm_unreachable("should not see this token");
|
|
}
|
|
llvm_unreachable("bogus token kind");
|
|
}
|
|
|
|
FullComment *Parser::parseFullComment() {
|
|
// Skip newlines at the beginning of the comment.
|
|
while (Tok.is(tok::newline))
|
|
consumeToken();
|
|
|
|
SmallVector<BlockContentComment *, 8> Blocks;
|
|
while (Tok.isNot(tok::eof)) {
|
|
Blocks.push_back(parseBlockContent());
|
|
|
|
// Skip extra newlines after paragraph end.
|
|
while (Tok.is(tok::newline))
|
|
consumeToken();
|
|
}
|
|
return S.actOnFullComment(S.copyArray(llvm::makeArrayRef(Blocks)));
|
|
}
|
|
|
|
} // end namespace comments
|
|
} // end namespace clang
|