diff options
Diffstat (limited to 'clang/lib/AST')
-rw-r--r-- | clang/lib/AST/ASTContext.cpp | 40 | ||||
-rw-r--r-- | clang/lib/AST/CMakeLists.txt | 5 | ||||
-rw-r--r-- | clang/lib/AST/Comment.cpp | 90 | ||||
-rw-r--r-- | clang/lib/AST/CommentDumper.cpp | 206 | ||||
-rw-r--r-- | clang/lib/AST/CommentLexer.cpp | 44 | ||||
-rw-r--r-- | clang/lib/AST/CommentParser.cpp | 414 | ||||
-rw-r--r-- | clang/lib/AST/CommentSema.cpp | 268 |
7 files changed, 1055 insertions, 12 deletions
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 66096f34e23..f65f9c0afc3 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13,6 +13,9 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/CharUnits.h" +#include "clang/AST/CommentLexer.h" +#include "clang/AST/CommentSema.h" +#include "clang/AST/CommentParser.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" @@ -149,18 +152,47 @@ const RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const { const RawComment *ASTContext::getRawCommentForDecl(const Decl *D) const { // Check whether we have cached a comment string for this declaration // already. - llvm::DenseMap<const Decl *, const RawComment *>::iterator Pos + llvm::DenseMap<const Decl *, RawAndParsedComment>::iterator Pos = DeclComments.find(D); - if (Pos != DeclComments.end()) - return Pos->second; + if (Pos != DeclComments.end()) { + RawAndParsedComment C = Pos->second; + return C.first; + } const RawComment *RC = getRawCommentForDeclNoCache(D); // If we found a comment, it should be a documentation comment. assert(!RC || RC->isDocumentation()); - DeclComments[D] = RC; + DeclComments[D] = RawAndParsedComment(RC, NULL); return RC; } +comments::FullComment *ASTContext::getCommentForDecl(const Decl *D) const { + llvm::DenseMap<const Decl *, RawAndParsedComment>::iterator Pos + = DeclComments.find(D); + const RawComment *RC; + if (Pos != DeclComments.end()) { + RawAndParsedComment C = Pos->second; + if (comments::FullComment *FC = C.second) + return FC; + RC = C.first; + } else + RC = getRawCommentForDecl(D); + + if (!RC) + return NULL; + + const StringRef RawText = RC->getRawText(SourceMgr); + comments::Lexer L(RC->getSourceRange().getBegin(), comments::CommentOptions(), + RawText.begin(), RawText.end()); + + comments::Sema S(this->BumpAlloc); + comments::Parser P(L, S, this->BumpAlloc); + + comments::FullComment *FC = P.parseFullComment(); + DeclComments[D].second = FC; + return FC; +} + void ASTContext::CanonicalTemplateTemplateParm::Profile(llvm::FoldingSetNodeID &ID, TemplateTemplateParmDecl *Parm) { diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index 5dad60c4909..c45f721f97d 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -8,8 +8,12 @@ add_clang_library(clangAST ASTImporter.cpp AttrImpl.cpp CXXInheritance.cpp + Comment.cpp CommentBriefParser.cpp + CommentDumper.cpp CommentLexer.cpp + CommentParser.cpp + CommentSema.cpp Decl.cpp DeclarationName.cpp DeclBase.cpp @@ -60,6 +64,7 @@ add_dependencies(clangAST ClangAttrList ClangAttrImpl ClangDiagnosticAST + ClangCommentNodes ClangDeclNodes ClangStmtNodes ) diff --git a/clang/lib/AST/Comment.cpp b/clang/lib/AST/Comment.cpp new file mode 100644 index 00000000000..22277ad88ae --- /dev/null +++ b/clang/lib/AST/Comment.cpp @@ -0,0 +1,90 @@ +//===--- Comment.cpp - Comment AST node implementation --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/Comment.h" +#include "llvm/Support/ErrorHandling.h" + +namespace clang { +namespace comments { + +const char *Comment::getCommentKindName() const { + switch (getCommentKind()) { + case NoCommentKind: return "NoCommentKind"; +#define ABSTRACT_COMMENT(COMMENT) +#define COMMENT(CLASS, PARENT) \ + case CLASS##Kind: \ + return #CLASS; +#include "clang/AST/CommentNodes.inc" +#undef COMMENT +#undef ABSTRACT_COMMENT + } + llvm_unreachable("Unknown comment kind!"); +} + +namespace { +struct good {}; +struct bad {}; + +template <typename T> +good implements_child_begin_end(Comment::child_iterator (T::*)() const) { + return good(); +} + +static inline bad implements_child_begin_end( + Comment::child_iterator (Comment::*)() const) { + return bad(); +} + +#define ASSERT_IMPLEMENTS_child_begin(function) \ + (void) sizeof(good(implements_child_begin_end(function))) + +static inline void CheckCommentASTNodes() { +#define ABSTRACT_COMMENT(COMMENT) +#define COMMENT(CLASS, PARENT) \ + ASSERT_IMPLEMENTS_child_begin(&CLASS::child_begin); \ + ASSERT_IMPLEMENTS_child_begin(&CLASS::child_end); +#include "clang/AST/CommentNodes.inc" +#undef COMMENT +#undef ABSTRACT_COMMENT +} + +#undef ASSERT_IMPLEMENTS_child_begin + +} // end unnamed namespace + +Comment::child_iterator Comment::child_begin() const { + switch (getCommentKind()) { + case NoCommentKind: llvm_unreachable("comment without a kind"); +#define ABSTRACT_COMMENT(COMMENT) +#define COMMENT(CLASS, PARENT) \ + case CLASS##Kind: \ + return static_cast<const CLASS *>(this)->child_begin(); +#include "clang/AST/CommentNodes.inc" +#undef COMMENT +#undef ABSTRACT_COMMENT + } +} + +Comment::child_iterator Comment::child_end() const { + switch (getCommentKind()) { + case NoCommentKind: llvm_unreachable("comment without a kind"); +#define ABSTRACT_COMMENT(COMMENT) +#define COMMENT(CLASS, PARENT) \ + case CLASS##Kind: \ + return static_cast<const CLASS *>(this)->child_end(); +#include "clang/AST/CommentNodes.inc" +#undef COMMENT +#undef ABSTRACT_COMMENT + } +} + + +} // end namespace comments +} // end namespace clang + diff --git a/clang/lib/AST/CommentDumper.cpp b/clang/lib/AST/CommentDumper.cpp new file mode 100644 index 00000000000..fd7a3942a43 --- /dev/null +++ b/clang/lib/AST/CommentDumper.cpp @@ -0,0 +1,206 @@ +//===--- CommentDumper.cpp - Dumping implementation for Comment ASTs ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/CommentVisitor.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace comments { + +namespace { +class CommentDumper: public comments::ConstCommentVisitor<CommentDumper> { + raw_ostream &OS; + SourceManager *SM; + unsigned IndentLevel; + +public: + CommentDumper(raw_ostream &OS, SourceManager *SM) : + OS(OS), SM(SM), IndentLevel(0) + { } + + void dumpIndent() const { + for (unsigned i = 1, e = IndentLevel; i < e; ++i) + OS << " "; + } + + void dumpLocation(SourceLocation Loc) { + if (SM) + Loc.print(OS, *SM); + } + + void dumpSourceRange(const Comment *C); + + void dumpComment(const Comment *C); + + void dumpSubtree(const Comment *C); + + // Inline content. + void visitTextComment(const TextComment *C); + void visitInlineCommandComment(const InlineCommandComment *C); + void visitHTMLOpenTagComment(const HTMLOpenTagComment *C); + void visitHTMLCloseTagComment(const HTMLCloseTagComment *C); + + // Block content. + void visitParagraphComment(const ParagraphComment *C); + void visitBlockCommandComment(const BlockCommandComment *C); + void visitParamCommandComment(const ParamCommandComment *C); + void visitVerbatimBlockComment(const VerbatimBlockComment *C); + void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); + void visitVerbatimLineComment(const VerbatimLineComment *C); + + void visitFullComment(const FullComment *C); +}; + +void CommentDumper::dumpSourceRange(const Comment *C) { + if (!SM) + return; + + SourceRange SR = C->getSourceRange(); + + OS << " <"; + dumpLocation(SR.getBegin()); + if (SR.getBegin() != SR.getEnd()) { + OS << ", "; + dumpLocation(SR.getEnd()); + } + OS << ">"; +} + +void CommentDumper::dumpComment(const Comment *C) { + dumpIndent(); + OS << "(" << C->getCommentKindName() + << " " << (void *) C; + dumpSourceRange(C); +} + +void CommentDumper::dumpSubtree(const Comment *C) { + ++IndentLevel; + if (C) { + visit(C); + for (Comment::child_iterator I = C->child_begin(), + E = C->child_end(); + I != E; ++I) { + OS << '\n'; + dumpSubtree(*I); + } + OS << ')'; + } else { + dumpIndent(); + OS << "<<<NULL>>>"; + } + --IndentLevel; +} + +void CommentDumper::visitTextComment(const TextComment *C) { + dumpComment(C); + + OS << " Text=\"" << C->getText() << "\""; +} + +void CommentDumper::visitInlineCommandComment(const InlineCommandComment *C) { + dumpComment(C); + + for (unsigned i = 0, e = C->getArgCount(); i != e; ++i) + OS << " Arg[" << i << "]=\"" << C->getArgText(i) << "\""; +} + +void CommentDumper::visitHTMLOpenTagComment(const HTMLOpenTagComment *C) { + dumpComment(C); + + OS << " Name=\"" << C->getTagName() << "\""; + if (C->getAttrCount() != 0) { + OS << " Attrs: "; + for (unsigned i = 0, e = C->getAttrCount(); i != e; ++i) { + const HTMLOpenTagComment::Attribute &Attr = C->getAttr(i); + OS << " \"" << Attr.Name << "=\"" << Attr.Value << "\""; + } + } +} + +void CommentDumper::visitHTMLCloseTagComment(const HTMLCloseTagComment *C) { + dumpComment(C); + + OS << " Name=\"" << C->getTagName() << "\""; +} + +void CommentDumper::visitParagraphComment(const ParagraphComment *C) { + dumpComment(C); +} + +void CommentDumper::visitBlockCommandComment(const BlockCommandComment *C) { + dumpComment(C); + + OS << " Name=\"" << C->getCommandName() << "\""; +} + +void CommentDumper::visitParamCommandComment(const ParamCommandComment *C) { + dumpComment(C); + + switch (C->getDirection()) { + case ParamCommandComment::In: + OS << " [in]"; + break; + case ParamCommandComment::Out: + OS << " [out]"; + break; + case ParamCommandComment::InOut: + OS << " [in,out]"; + break; + } + + if (C->isDirectionExplicit()) + OS << " explicitly"; + else + OS << " implicitly"; + + if (C->hasParamName()) { + OS << " Param=\"" << C->getParamName() << "\""; + } +} + +void CommentDumper::visitVerbatimBlockComment(const VerbatimBlockComment *C) { + dumpComment(C); + + OS << " Name=\"" << C->getCommandName() << "\"" + " CloseName=\"" << C->getCloseName() << "\""; +} + +void CommentDumper::visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C) { + dumpComment(C); + + OS << " Text=\"" << C->getText() << "\""; +} + +void CommentDumper::visitVerbatimLineComment(const VerbatimLineComment *C) { + dumpComment(C); + + OS << " Text=\"" << C->getText() << "\""; +} + +void CommentDumper::visitFullComment(const FullComment *C) { + dumpComment(C); +} + +} // unnamed namespace + +void Comment::dump() const { + CommentDumper D(llvm::errs(), NULL); + D.dumpSubtree(this); + llvm::errs() << '\n'; +} + +void Comment::dump(SourceManager &SM) const { + CommentDumper D(llvm::errs(), &SM); + D.dumpSubtree(this); + llvm::errs() << '\n'; +} + +} // end namespace comments +} // end namespace clang + diff --git a/clang/lib/AST/CommentLexer.cpp b/clang/lib/AST/CommentLexer.cpp index c3a801d924c..77d2a9b72dd 100644 --- a/clang/lib/AST/CommentLexer.cpp +++ b/clang/lib/AST/CommentLexer.cpp @@ -122,6 +122,7 @@ void Lexer::skipLineStartingDecorations() { } namespace { +/// Returns pointer to the first newline character in the string. const char *findNewline(const char *BufferPtr, const char *BufferEnd) { for ( ; BufferPtr != BufferEnd; ++BufferPtr) { const char C = *BufferPtr; @@ -270,6 +271,9 @@ void Lexer::lexCommentText(Token &T) { case LS_HTMLOpenTag: lexHTMLOpenTag(T); return; + case LS_HTMLCloseTag: + lexHTMLCloseTag(T); + return; } assert(State == LS_Normal); @@ -356,7 +360,7 @@ void Lexer::lexCommentText(Token &T) { if (isHTMLIdentifierCharacter(C)) setupAndLexHTMLOpenTag(T); else if (C == '/') - lexHTMLCloseTag(T); + setupAndLexHTMLCloseTag(T); else { StringRef Text(BufferPtr, TokenPtr - BufferPtr); formTokenWithChars(T, TokenPtr, tok::text); @@ -404,6 +408,18 @@ void Lexer::setupAndLexVerbatimBlock(Token &T, formTokenWithChars(T, TextBegin, tok::verbatim_block_begin); T.setVerbatimBlockName(Name); + // If there is a newline following the verbatim opening command, skip the + // newline so that we don't create an tok::verbatim_block_line with empty + // text content. + if (BufferPtr != CommentEnd) { + const char C = *BufferPtr; + if (C == '\n' || C == '\r') { + BufferPtr = skipNewline(BufferPtr, CommentEnd); + State = LS_VerbatimBlockBody; + return; + } + } + State = LS_VerbatimBlockFirstLine; } @@ -419,9 +435,11 @@ void Lexer::lexVerbatimBlockFirstLine(Token &T) { // Look for end command in current line. size_t Pos = Line.find(VerbatimBlockEndCommandName); + const char *TextEnd; const char *NextLine; if (Pos == StringRef::npos) { // Current line is completely verbatim. + TextEnd = Newline; NextLine = skipNewline(Newline, CommentEnd); } else if (Pos == 0) { // Current line contains just an end command. @@ -433,10 +451,11 @@ void Lexer::lexVerbatimBlockFirstLine(Token &T) { return; } else { // There is some text, followed by end command. Extract text first. - NextLine = BufferPtr + Pos; + TextEnd = BufferPtr + Pos; + NextLine = TextEnd; } - StringRef Text(BufferPtr, NextLine - BufferPtr); + StringRef Text(BufferPtr, TextEnd - BufferPtr); formTokenWithChars(T, NextLine, tok::verbatim_block_line); T.setVerbatimBlockText(Text); @@ -542,18 +561,26 @@ void Lexer::lexHTMLOpenTag(Token &T) { } } -void Lexer::lexHTMLCloseTag(Token &T) { +void Lexer::setupAndLexHTMLCloseTag(Token &T) { assert(BufferPtr[0] == '<' && BufferPtr[1] == '/'); const char *TagNameBegin = skipWhitespace(BufferPtr + 2, CommentEnd); const char *TagNameEnd = skipHTMLIdentifier(TagNameBegin, CommentEnd); const char *End = skipWhitespace(TagNameEnd, CommentEnd); - if (End != CommentEnd && *End == '>') - End++; formTokenWithChars(T, End, tok::html_tag_close); T.setHTMLTagCloseName(StringRef(TagNameBegin, TagNameEnd - TagNameBegin)); + + if (BufferPtr != CommentEnd && *BufferPtr == '>') + State = LS_HTMLCloseTag; +} + +void Lexer::lexHTMLCloseTag(Token &T) { + assert(BufferPtr != CommentEnd && *BufferPtr == '>'); + + formTokenWithChars(T, BufferPtr + 1, tok::html_greater); + State = LS_Normal; } Lexer::Lexer(SourceLocation FileLoc, const CommentOptions &CommOpts, @@ -595,7 +622,8 @@ again: BufferPtr++; CommentState = LCS_InsideBCPLComment; - State = LS_Normal; + if (State != LS_VerbatimBlockBody && State != LS_VerbatimBlockFirstLine) + State = LS_Normal; CommentEnd = findBCPLCommentEnd(BufferPtr, BufferEnd); goto again; } @@ -628,7 +656,7 @@ again: EndWhitespace++; // Turn any whitespace between comments (and there is only whitespace - // between them) into a newline. We have two newlines between comments + // between them) into a newline. We have two newlines between C comments // in total (first one was synthesized after a comment). formTokenWithChars(T, EndWhitespace, tok::newline); diff --git a/clang/lib/AST/CommentParser.cpp b/clang/lib/AST/CommentParser.cpp new file mode 100644 index 00000000000..701b6fa1e1a --- /dev/null +++ b/clang/lib/AST/CommentParser.cpp @@ -0,0 +1,414 @@ +//===--- CommentParser.cpp - Doxygen comment parser -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/CommentParser.h" +#include "clang/AST/CommentSema.h" +#include "llvm/Support/ErrorHandling.h" + +namespace clang { +namespace comments { + +Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator): + L(L), S(S), Allocator(Allocator) { + consumeToken(); +} + +ParamCommandComment *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, '[', ']')) + PC = S.actOnParamCommandArg(PC, + Arg.getLocation(), + Arg.getEndLocation(), + Arg.getText(), + /* IsDirection = */ true); + + if (Retokenizer.lexWord(Arg)) + StringRef ArgText = Arg.getText(); + PC = S.actOnParamCommandArg(PC, + Arg.getLocation(), + Arg.getEndLocation(), + Arg.getText(), + /* IsDirection = */ false); + + return PC; +} + +BlockCommandComment *Parser::parseBlockCommandArgs( + BlockCommandComment *BC, + TextTokenRetokenizer &Retokenizer, + unsigned NumArgs) { + typedef BlockCommandComment::Argument Argument; + Argument *Args = new (Allocator) Argument[NumArgs]; + unsigned ParsedArgs = 0; + Token Arg; + while (ParsedArgs < NumArgs && Retokenizer.lexWord(Arg)) { + Args[ParsedArgs] = Argument(SourceRange(Arg.getLocation(), + Arg.getEndLocation()), + Arg.getText()); + ParsedArgs++; + } + + return S.actOnBlockCommandArgs(BC, llvm::makeArrayRef(Args, ParsedArgs)); +} + +BlockCommandComment *Parser::parseBlockCommand() { + assert(Tok.is(tok::command)); + + ParamCommandComment *PC; + BlockCommandComment *BC; + bool IsParam = false; + unsigned NumArgs = 0; + if (S.isParamCommand(Tok.getCommandName())) { + IsParam = true; + PC = S.actOnParamCommandStart(Tok.getLocation(), + Tok.getEndLocation(), + Tok.getCommandName()); + } else { + NumArgs = S.getBlockCommandNumArgs(Tok.getCommandName()); + BC = S.actOnBlockCommandStart(Tok.getLocation(), + Tok.getEndLocation(), + Tok.getCommandName()); + } + consumeToken(); + + if (Tok.is(tok::command) && S.isBlockCommand(Tok.getCommandName())) { + // Block command ahead. We can't nest block commands, so pretend that this + // command has an empty argument. + // TODO: Diag() Warn empty arg to block command + ParagraphComment *PC = S.actOnParagraphComment( + ArrayRef<InlineContentComment *>()); + return S.actOnBlockCommandFinish(BC, PC); + } + + if (IsParam || NumArgs > 0) { + // In order to parse command arguments we need to retokenize a few + // following text tokens. + TextTokenRetokenizer Retokenizer(Allocator); + while (Tok.is(tok::text)) { + if (Retokenizer.addToken(Tok)) + consumeToken(); + } + + if (IsParam) + PC = parseParamCommandArgs(PC, Retokenizer); + else + BC = parseBlockCommandArgs(BC, Retokenizer, NumArgs); + + // Put back tokens we didn't use. + Token Text; + while (Retokenizer.lexText(Text)) + putBack(Text); + } + + BlockContentComment *Block = parseParagraphOrBlockCommand(); + // Since we have checked for a block command, we should have parsed a + // paragraph. + if (IsParam) + return S.actOnParamCommandFinish(PC, cast<ParagraphComment>(Block)); + else + return S.actOnBlockCommandFinish(BC, cast<ParagraphComment>(Block)); +} + +InlineCommandComment *Parser::parseInlineCommand() { + assert(Tok.is(tok::command)); + + const Token CommandTok = Tok; + consumeToken(); + + TextTokenRetokenizer Retokenizer(Allocator); + while (Tok.is(tok::text)) { + if (Retokenizer.addToken(Tok)) + consumeToken(); + } + + Token ArgTok; + bool ArgTokValid = Retokenizer.lexWord(ArgTok); + + InlineCommandComment *IC; + if (ArgTokValid) { + IC = S.actOnInlineCommand(CommandTok.getLocation(), + CommandTok.getEndLocation(), + CommandTok.getCommandName(), + ArgTok.getLocation(), + ArgTok.getEndLocation(), + ArgTok.getText()); + } else { + IC = S.actOnInlineCommand(CommandTok.getLocation(), + CommandTok.getEndLocation(), + CommandTok.getCommandName()); + } + + Token Text; + while (Retokenizer.lexText(Text)) + putBack(Text); + + return IC; +} + +HTMLOpenTagComment *Parser::parseHTMLOpenTag() { + assert(Tok.is(tok::html_tag_open)); + HTMLOpenTagComment *HOT = + S.actOnHTMLOpenTagStart(Tok.getLocation(), + Tok.getHTMLTagOpenName()); + consumeToken(); + + SmallVector<HTMLOpenTagComment::Attribute, 2> Attrs; + while (true) { + if (Tok.is(tok::html_ident)) { + Token Ident = Tok; + consumeToken(); + if (Tok.isNot(tok::html_equals)) { + Attrs.push_back(HTMLOpenTagComment::Attribute(Ident.getLocation(), + Ident.getHTMLIdent())); + continue; + } + Token Equals = Tok; + consumeToken(); + if (Tok.isNot(tok::html_quoted_string)) { + // TODO: Diag() expected quoted string + Attrs.push_back(HTMLOpenTagComment::Attribute(Ident.getLocation(), + Ident.getHTMLIdent())); + continue; + } + Attrs.push_back(HTMLOpenTagComment::Attribute( + Ident.getLocation(), + Ident.getHTMLIdent(), + Equals.getLocation(), + SourceRange(Tok.getLocation(), + Tok.getEndLocation()), + Tok.getHTMLQuotedString())); + consumeToken(); + continue; + } else if (Tok.is(tok::html_greater)) { + HOT = S.actOnHTMLOpenTagFinish(HOT, + copyArray(llvm::makeArrayRef(Attrs)), + Tok.getLocation()); + consumeToken(); + return HOT; + } else if (Tok.is(tok::html_equals) || + Tok.is(tok::html_quoted_string)) { + // TODO: Diag() Err expected ident + while (Tok.is(tok::html_equals) || + Tok.is(tok::html_quoted_string)) + consumeToken(); + } else { + // Not a token from HTML open tag. Thus HTML tag prematurely ended. + // TODO: Diag() Err HTML tag prematurely ended + return S.actOnHTMLOpenTagFinish(HOT, + copyArray(llvm::makeArrayRef(Attrs)), + SourceLocation()); + } + } +} + +HTMLCloseTagComment *Parser::parseHTMLCloseTag() { + assert(Tok.is(tok::html_tag_close)); + Token TokTagOpen = Tok; + consumeToken(); + SourceLocation Loc; + if (Tok.is(tok::html_greater)) { + Loc = Tok.getLocation(); + consumeToken(); + } + + return S.actOnHTMLCloseTag(TokTagOpen.getLocation(), + Loc, + TokTagOpen.getHTMLTagCloseName()); +} + +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: + assert(Content.size() != 0); + break; // Block content or EOF ahead, finish this parapgaph. + + case tok::command: + if (S.isBlockCommand(Tok.getCommandName())) { + if (Content.size() == 0) + return parseBlockCommand(); + break; // Block command ahead, finish this parapgaph. + } + if (S.isInlineCommand(Tok.getCommandName())) { + Content.push_back(parseInlineCommand()); + continue; + } + + // Not a block command, not an inline command ==> an unknown command. + Content.push_back(S.actOnUnknownCommand(Tok.getLocation(), + Tok.getEndLocation(), + Tok.getCommandName())); + consumeToken(); + continue; + + case tok::newline: { + consumeToken(); + if (Tok.is(tok::newline) || Tok.is(tok::eof)) { + consumeToken(); + break; // Two newlines -- end of paragraph. + } + if (Content.size() > 0) + Content.back()->addTrailingNewline(); + continue; + } + + // Don't deal with HTML tag soup now. + case tok::html_tag_open: + Content.push_back(parseHTMLOpenTag()); + continue; + + case tok::html_tag_close: + Content.push_back(parseHTMLCloseTag()); + 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: + llvm_unreachable("should not see this token"); + } + break; + } + + return S.actOnParagraphComment(copyArray(llvm::makeArrayRef(Content))); +} + +VerbatimBlockComment *Parser::parseVerbatimBlock() { + assert(Tok.is(tok::verbatim_block_begin)); + + VerbatimBlockComment *VB = + S.actOnVerbatimBlockStart(Tok.getLocation(), + Tok.getVerbatimBlockName()); + 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); + } + + assert(Tok.is(tok::verbatim_block_end)); + VB = S.actOnVerbatimBlockFinish(VB, Tok.getLocation(), + Tok.getVerbatimBlockName(), + copyArray(llvm::makeArrayRef(Lines))); + consumeToken(); + + 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.getVerbatimLineName(), + TextBegin, + Text); + consumeToken(); + return VL; +} + +BlockContentComment *Parser::parseBlockContent() { + switch (Tok.getKind()) { + case tok::text: + case tok::command: + case tok::html_tag_open: + case tok::html_tag_close: + 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: + llvm_unreachable("should not see this token"); + } +} + +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(copyArray(llvm::makeArrayRef(Blocks))); +} + +} // end namespace comments +} // end namespace clang + + diff --git a/clang/lib/AST/CommentSema.cpp b/clang/lib/AST/CommentSema.cpp new file mode 100644 index 00000000000..1193e0404a0 --- /dev/null +++ b/clang/lib/AST/CommentSema.cpp @@ -0,0 +1,268 @@ +//===--- CommentSema.cpp - Doxygen comment semantic analysis --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/CommentSema.h" +#include "llvm/ADT/StringSwitch.h" + +namespace clang { +namespace comments { + +Sema::Sema(llvm::BumpPtrAllocator &Allocator) : + Allocator(Allocator) { +} + +ParagraphComment *Sema::actOnParagraphComment( + ArrayRef<InlineContentComment *> Content) { + return new (Allocator) ParagraphComment(Content); +} + +BlockCommandComment *Sema::actOnBlockCommandStart(SourceLocation LocBegin, + SourceLocation LocEnd, + StringRef Name) { + return new (Allocator) BlockCommandComment(LocBegin, LocEnd, Name); +} + +BlockCommandComment *Sema::actOnBlockCommandArgs( + BlockCommandComment *Command, + ArrayRef<BlockCommandComment::Argument> Args) { + Command->setArgs(Args); + return Command; +} + +BlockCommandComment *Sema::actOnBlockCommandFinish( + BlockCommandComment *Command, + ParagraphComment *Paragraph) { + Command->setParagraph(Paragraph); + return Command; +} + +ParamCommandComment *Sema::actOnParamCommandStart(SourceLocation LocBegin, + SourceLocation LocEnd, + StringRef Name) { + return new (Allocator) ParamCommandComment(LocBegin, LocEnd, Name); +} + +ParamCommandComment *Sema::actOnParamCommandArg(ParamCommandComment *Command, + SourceLocation ArgLocBegin, + SourceLocation ArgLocEnd, + StringRef Arg, + bool IsDirection) { + if (IsDirection) { + ParamCommandComment::PassDirection Direction; + std::string ArgLower = Arg.lower(); + // TODO: optimize: lower Name first (need an API in SmallString for that), + // after that StringSwitch. + if (ArgLower == "[in]") + Direction = ParamCommandComment::In; + else if (ArgLower == "[out]") + Direction = ParamCommandComment::Out; + else if (ArgLower == "[in,out]" || ArgLower == "[out,in]") + Direction = ParamCommandComment::InOut; + else { + // Remove spaces. + std::string::iterator O = ArgLower.begin(); + for (std::string::iterator I = ArgLower.begin(), E = ArgLower.end(); + I != E; ++I) { + const char C = *I; + if (C != ' ' && C != '\n' && C != '\r' && + C != '\t' && C != '\v' && C != '\f') + *O++ = C; + } + ArgLower.resize(O - ArgLower.begin()); + + bool RemovingWhitespaceHelped = false; + if (ArgLower == "[in]") { + Direction = ParamCommandComment::In; + RemovingWhitespaceHelped = true; + } else if (ArgLower == "[out]") { + Direction = ParamCommandComment::Out; + RemovingWhitespaceHelped = true; + } else if (ArgLower == "[in,out]" || ArgLower == "[out,in]") { + Direction = ParamCommandComment::InOut; + RemovingWhitespaceHelped = true; + } else { + Direction = ParamCommandComment::In; + RemovingWhitespaceHelped = false; + } + // Diag() unrecognized parameter passing direction, valid directions are ... + // if (RemovingWhitespaceHelped) FixIt + } + Command->setDirection(Direction, /* Explicit = */ true); + } else { + if (Command->getArgCount() == 0) { + if (!Command->isDirectionExplicit()) { + // User didn't provide a direction argument. + Command->setDirection(ParamCommandComment::In, /* Explicit = */ false); + } + typedef BlockCommandComment::Argument Argument; + Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin, + ArgLocEnd), + Arg); + Command->setArgs(llvm::makeArrayRef(A, 1)); + // if (...) Diag() unrecognized parameter name + } else { + // Diag() \\param command requires at most 2 arguments + } + } + return Command; +} + +ParamCommandComment *Sema::actOnParamCommandFinish(ParamCommandComment *Command, + ParagraphComment *Paragraph) { + Command->setParagraph(Paragraph); + return Command; +} + +InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin, + SourceLocation CommandLocEnd, + StringRef CommandName) { + ArrayRef<InlineCommandComment::Argument> Args; + return new (Allocator) InlineCommandComment(CommandLocBegin, + CommandLocEnd, + CommandName, + Args); +} + +InlineCommandComment *Sema::actOnInlineCommand(SourceLocation CommandLocBegin, + SourceLocation CommandLocEnd, + StringRef CommandName, + SourceLocation ArgLocBegin, + SourceLocation ArgLocEnd, + StringRef Arg) { + typedef InlineCommandComment::Argument Argument; + Argument *A = new (Allocator) Argument(SourceRange(ArgLocBegin, + ArgLocEnd), + Arg); + + return new (Allocator) InlineCommandComment(CommandLocBegin, + CommandLocEnd, + CommandName, + llvm::makeArrayRef(A, 1)); +} + +InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin, + SourceLocation LocEnd, + StringRef Name) { + ArrayRef<InlineCommandComment::Argument> Args; + return new (Allocator) InlineCommandComment(LocBegin, LocEnd, Name, Args); +} + +TextComment *Sema::actOnText(SourceLocation LocBegin, + SourceLocation LocEnd, + StringRef Text) { + return new (Allocator) TextComment(LocBegin, LocEnd, Text); +} + +VerbatimBlockComment *Sema::actOnVerbatimBlockStart(SourceLocation Loc, + StringRef Name) { + return new (Allocator) VerbatimBlockComment( + Loc, + Loc.getLocWithOffset(1 + Name.size()), + Name); +} + +VerbatimBlockLineComment *Sema::actOnVerbatimBlockLine(SourceLocation Loc, + StringRef Text) { + return new (Allocator) VerbatimBlockLineComment(Loc, Text); +} + +VerbatimBlockComment *Sema::actOnVerbatimBlockFinish( + VerbatimBlockComment *Block, + SourceLocation CloseNameLocBegin, + StringRef CloseName, + ArrayRef<VerbatimBlockLineComment *> Lines) { + Block->setCloseName(CloseName, CloseNameLocBegin); + Block->setLines(Lines); + return Block; +} + +VerbatimLineComment *Sema::actOnVerbatimLine(SourceLocation LocBegin, + StringRef Name, + SourceLocation TextBegin, + StringRef Text) { + return new (Allocator) VerbatimLineComment( + LocBegin, + TextBegin.getLocWithOffset(Text.size()), + Name, + TextBegin, + Text); +} + +HTMLOpenTagComment *Sema::actOnHTMLOpenTagStart(SourceLocation LocBegin, + StringRef TagName) { + return new (Allocator) HTMLOpenTagComment(LocBegin, TagName); +} + +HTMLOpenTagComment *Sema::actOnHTMLOpenTagFinish( + HTMLOpenTagComment *Tag, + ArrayRef<HTMLOpenTagComment::Attribute> Attrs, + SourceLocation GreaterLoc) { + Tag->setAttrs(Attrs); + Tag->setGreaterLoc(GreaterLoc); + return Tag; +} + +HTMLCloseTagComment *Sema::actOnHTMLCloseTag(SourceLocation LocBegin, + SourceLocation LocEnd, + StringRef TagName) { + return new (Allocator) HTMLCloseTagComment(LocBegin, LocEnd, TagName); +} + +FullComment *Sema::actOnFullComment( + ArrayRef<BlockContentComment *> Blocks) { + return new (Allocator) FullComment(Blocks); +} + +// TODO: tablegen +bool Sema::isBlockCommand(StringRef Name) { + return llvm::StringSwitch<bool>(Name) + .Case("brief", true) + .Case("result", true) + .Case("return", true) + .Case("returns", true) + .Case("author", true) + .Case("authors", true) + .Case("pre", true) + .Case("post", true) + .Default(false) || isParamCommand(Name); +} + +bool Sema::isParamCommand(StringRef Name) { + return llvm::StringSwitch<bool>(Name) + .Case("param", true) + .Case("arg", true) + .Default(false); +} + +unsigned Sema::getBlockCommandNumArgs(StringRef Name) { + return llvm::StringSwitch<unsigned>(Name) + .Case("brief", 0) + .Case("pre", 0) + .Case("post", 0) + .Case("author", 0) + .Case("authors", 0) + .Default(0); +} + +bool Sema::isInlineCommand(StringRef Name) { + return llvm::StringSwitch<bool>(Name) + .Case("c", true) + .Case("em", true) + .Default(false); +} + +bool Sema::HTMLOpenTagNeedsClosing(StringRef Name) { + return llvm::StringSwitch<bool>(Name) + .Case("br", true) + .Default(true); +} + +} // end namespace comments +} // end namespace clang + |