Add libclang APIs to walk comments ASTs and an API to convert a comment to an
HTML fragment.

For testing, c-index-test now has even more output:
* HTML rendering of a comment
* comment AST tree dump in S-expressions like Comment::dump(), but implemented
* with libclang APIs.


git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@160577 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/tools/c-index-test/c-index-test.c b/tools/c-index-test/c-index-test.c
index b3b5b84..bcfbece 100644
--- a/tools/c-index-test/c-index-test.c
+++ b/tools/c-index-test/c-index-test.c
@@ -183,8 +183,9 @@
 /* Pretty-printing.                                                           */
 /******************************************************************************/
 
-static void PrintCString(const char *Prefix, const char *CStr) {
-  printf(" %s=[", Prefix);
+static const char *FileCheckPrefix = "CHECK";
+
+static void PrintCString(const char *CStr) {
   if (CStr != NULL && CStr[0] != '\0') {
     for ( ; *CStr; ++CStr) {
       const char C = *CStr;
@@ -198,9 +199,25 @@
       }
     }
   }
+}
+
+static void PrintCStringWithPrefix(const char *Prefix, const char *CStr) {
+  printf(" %s=[", Prefix);
+  PrintCString(CStr);
   printf("]");
 }
 
+static void PrintCXStringAndDispose(CXString Str) {
+  PrintCString(clang_getCString(Str));
+  clang_disposeString(Str);
+}
+
+static void PrintCXStringWithPrefixAndDispose(const char *Prefix,
+                                              CXString Str) {
+  PrintCStringWithPrefix(Prefix, clang_getCString(Str));
+  clang_disposeString(Str);
+}
+
 static void PrintRange(CXSourceRange R, const char *str) {
   CXFile begin_file, end_file;
   unsigned begin_line, begin_column, end_line, end_column;
@@ -233,6 +250,188 @@
   printf(".%d", Version.Subminor);
 }
 
+struct CommentASTDumpingContext {
+  int IndentLevel;
+};
+
+static void DumpCXCommentInternal(struct CommentASTDumpingContext *Ctx,
+                                  CXComment Comment) {
+  Ctx->IndentLevel++;
+  for (unsigned i = 0, e = Ctx->IndentLevel; i != e; ++i)
+    printf("  ");
+
+  printf("(");
+  enum CXCommentKind Kind = clang_Comment_getKind(Comment);
+  switch (Kind) {
+  case CXComment_Null:
+    printf("CXComment_Null");
+    break;
+  case CXComment_Text:
+    printf("CXComment_Text");
+    PrintCXStringWithPrefixAndDispose("Text",
+                                      clang_TextComment_getText(Comment));
+    if (clang_Comment_isWhitespace(Comment))
+      printf(" IsWhitespace");
+    if (clang_InlineContentComment_hasTrailingNewline(Comment))
+      printf(" HasTrailingNewline");
+    break;
+  case CXComment_InlineCommand:
+    printf("CXComment_InlineCommand");
+    PrintCXStringWithPrefixAndDispose(
+        "CommandName",
+        clang_InlineCommandComment_getCommandName(Comment));
+    for (unsigned i = 0, e = clang_InlineCommandComment_getNumArgs(Comment);
+         i != e; ++i) {
+      printf(" Arg[%u]=", i);
+      PrintCXStringAndDispose(
+          clang_InlineCommandComment_getArgText(Comment, i));
+    }
+    if (clang_InlineContentComment_hasTrailingNewline(Comment))
+      printf(" HasTrailingNewline");
+    break;
+  case CXComment_HTMLStartTag:
+    printf("CXComment_HTMLStartTag");
+    PrintCXStringWithPrefixAndDispose(
+        "Name",
+        clang_HTMLTagComment_getTagName(Comment));
+    const unsigned NumAttrs = clang_HTMLStartTag_getNumAttrs(Comment);
+    if (NumAttrs != 0) {
+      printf(" Attrs:");
+      for (unsigned i = 0; i != NumAttrs; ++i) {
+        printf(" ");
+        PrintCXStringAndDispose(clang_HTMLStartTag_getAttrName(Comment, i));
+        printf("=");
+        PrintCXStringAndDispose(clang_HTMLStartTag_getAttrValue(Comment, i));
+      }
+    }
+    if (clang_HTMLStartTagComment_isSelfClosing(Comment))
+      printf(" SelfClosing");
+    if (clang_InlineContentComment_hasTrailingNewline(Comment))
+      printf(" HasTrailingNewline");
+    break;
+  case CXComment_HTMLEndTag:
+    printf("CXComment_HTMLEndTag");
+    PrintCXStringWithPrefixAndDispose(
+        "Name",
+        clang_HTMLTagComment_getTagName(Comment));
+    if (clang_InlineContentComment_hasTrailingNewline(Comment))
+      printf(" HasTrailingNewline");
+    break;
+  case CXComment_Paragraph:
+    printf("CXComment_Paragraph");
+    if (clang_Comment_isWhitespace(Comment))
+      printf(" IsWhitespace");
+    break;
+  case CXComment_BlockCommand:
+    printf("CXComment_BlockCommand");
+    PrintCXStringWithPrefixAndDispose(
+        "CommandName",
+        clang_BlockCommandComment_getCommandName(Comment));
+    for (unsigned i = 0, e = clang_BlockCommandComment_getNumArgs(Comment);
+         i != e; ++i) {
+      printf(" Arg[%u]=", i);
+      PrintCXStringAndDispose(
+          clang_BlockCommandComment_getArgText(Comment, i));
+    }
+    break;
+  case CXComment_ParamCommand:
+    printf("CXComment_ParamCommand");
+    switch (clang_ParamCommandComment_getDirection(Comment)) {
+    case CXCommentParamPassDirection_In:
+      printf(" in");
+      break;
+    case CXCommentParamPassDirection_Out:
+      printf(" out");
+      break;
+    case CXCommentParamPassDirection_InOut:
+      printf(" in,out");
+      break;
+    }
+    if (clang_ParamCommandComment_isDirectionExplicit(Comment))
+      printf(" explicitly");
+    else
+      printf(" implicitly");
+    PrintCXStringWithPrefixAndDispose(
+        "ParamName",
+        clang_ParamCommandComment_getParamName(Comment));
+    if (clang_ParamCommandComment_isParamIndexValid(Comment))
+      printf(" ParamIndex=%u", clang_ParamCommandComment_getParamIndex(Comment));
+    else
+      printf(" ParamIndex=Invalid");
+    break;
+  case CXComment_VerbatimBlockCommand:
+    printf("CXComment_VerbatimBlockCommand");
+    PrintCXStringWithPrefixAndDispose(
+        "CommandName",
+        clang_BlockCommandComment_getCommandName(Comment));
+    break;
+  case CXComment_VerbatimBlockLine:
+    printf("CXComment_VerbatimBlockLine");
+    PrintCXStringWithPrefixAndDispose(
+        "Text",
+        clang_VerbatimBlockLineComment_getText(Comment));
+    break;
+  case CXComment_VerbatimLine:
+    printf("CXComment_VerbatimLine");
+    PrintCXStringWithPrefixAndDispose(
+        "Text",
+        clang_VerbatimLineComment_getText(Comment));
+    break;
+  case CXComment_FullComment:
+    printf("CXComment_FullComment");
+    break;
+  }
+  if (Kind != CXComment_Null) {
+    const unsigned NumChildren = clang_Comment_getNumChildren(Comment);
+    for (unsigned i = 0; i != NumChildren; ++i) {
+      printf("\n// %s: ", FileCheckPrefix);
+      DumpCXCommentInternal(Ctx, clang_Comment_getChild(Comment, i));
+    }
+  }
+  printf(")");
+  Ctx->IndentLevel--;
+}
+
+static void DumpCXComment(CXComment Comment) {
+  struct CommentASTDumpingContext Ctx;
+  Ctx.IndentLevel = 1;
+  printf("\n// %s:  CommentAST=[\n// %s:", FileCheckPrefix, FileCheckPrefix);
+  DumpCXCommentInternal(&Ctx, Comment);
+  printf("]");
+}
+
+static void PrintCursorComments(CXCursor Cursor) {
+  {
+    CXString RawComment;
+    const char *RawCommentCString;
+    CXString BriefComment;
+    const char *BriefCommentCString;
+
+    RawComment = clang_Cursor_getRawCommentText(Cursor);
+    RawCommentCString = clang_getCString(RawComment);
+    if (RawCommentCString != NULL && RawCommentCString[0] != '\0') {
+      PrintCStringWithPrefix("RawComment", RawCommentCString);
+      PrintRange(clang_Cursor_getCommentRange(Cursor), "RawCommentRange");
+
+      BriefComment = clang_Cursor_getBriefCommentText(Cursor);
+      BriefCommentCString = clang_getCString(BriefComment);
+      if (BriefCommentCString != NULL && BriefCommentCString[0] != '\0')
+        PrintCStringWithPrefix("BriefComment", BriefCommentCString);
+      clang_disposeString(BriefComment);
+    }
+    clang_disposeString(RawComment);
+  }
+
+  {
+    CXComment Comment = clang_Cursor_getParsedComment(Cursor);
+    if (clang_Comment_getKind(Comment) != CXComment_Null) {
+      PrintCXStringWithPrefixAndDispose("FullCommentAsHTML",
+                                        clang_FullComment_getAsHTML(Comment));
+      DumpCXComment(Comment);
+    }
+  }
+}
+
 static void PrintCursor(CXCursor Cursor) {
   CXTranslationUnit TU = clang_Cursor_getTranslationUnit(Cursor);
   if (clang_isInvalid(Cursor.kind)) {
@@ -257,10 +456,6 @@
     CXPlatformAvailability PlatformAvailability[2];
     int NumPlatformAvailability;
     int I;
-    CXString RawComment;
-    const char *RawCommentCString;
-    CXString BriefComment;
-    const char *BriefCommentCString;
 
     ks = clang_getCursorKindSpelling(Cursor.kind);
     string = want_display_name? clang_getCursorDisplayName(Cursor) 
@@ -442,19 +637,7 @@
         PrintRange(RefNameRange, "RefName");
     }
 
-    RawComment = clang_Cursor_getRawCommentText(Cursor);
-    RawCommentCString = clang_getCString(RawComment);
-    if (RawCommentCString != NULL && RawCommentCString[0] != '\0') {
-      PrintCString("RawComment", RawCommentCString);
-      PrintRange(clang_Cursor_getCommentRange(Cursor), "RawCommentRange");
-
-      BriefComment = clang_Cursor_getBriefCommentText(Cursor);
-      BriefCommentCString = clang_getCString(BriefComment);
-      if (BriefCommentCString != NULL && BriefCommentCString[0] != '\0')
-        PrintCString("BriefComment", BriefCommentCString);
-      clang_disposeString(BriefComment);
-    }
-    clang_disposeString(RawComment);
+    PrintCursorComments(Cursor);
   }
 }
 
@@ -577,8 +760,6 @@
 /* Logic for testing traversal.                                               */
 /******************************************************************************/
 
-static const char *FileCheckPrefix = "CHECK";
-
 static void PrintCursorExtent(CXCursor C) {
   CXSourceRange extent = clang_getCursorExtent(C);
   PrintRange(extent, "Extent");
diff --git a/tools/libclang/CIndex.cpp b/tools/libclang/CIndex.cpp
index 3b3b697..b1e4bad 100644
--- a/tools/libclang/CIndex.cpp
+++ b/tools/libclang/CIndex.cpp
@@ -13,6 +13,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CIndexer.h"
+#include "CXComment.h"
 #include "CXCursor.h"
 #include "CXTranslationUnit.h"
 #include "CXString.h"
@@ -5729,6 +5730,17 @@
   return createCXString((const char *) NULL);
 }
 
+CXComment clang_Cursor_getParsedComment(CXCursor C) {
+  if (!clang_isDeclaration(C.kind))
+    return cxcomment::createCXComment(NULL);
+
+  const Decl *D = getCursorDecl(C);
+  const ASTContext &Context = getCursorContext(C);
+  const comments::FullComment *FC = Context.getCommentForDecl(D);
+
+  return cxcomment::createCXComment(FC);
+}
+
 } // end: extern "C"
 
 //===----------------------------------------------------------------------===//
diff --git a/tools/libclang/CMakeLists.txt b/tools/libclang/CMakeLists.txt
index 2fcbf5b..6270038 100644
--- a/tools/libclang/CMakeLists.txt
+++ b/tools/libclang/CMakeLists.txt
@@ -15,6 +15,7 @@
   CIndexUSRs.cpp
   CIndexer.cpp
   CIndexer.h
+  CXComment.cpp
   CXCursor.cpp
   CXCursor.h
   CXCompilationDatabase.cpp
diff --git a/tools/libclang/CXComment.cpp b/tools/libclang/CXComment.cpp
new file mode 100644
index 0000000..8d01a41
--- /dev/null
+++ b/tools/libclang/CXComment.cpp
@@ -0,0 +1,657 @@
+//===- CXComment.cpp - libclang APIs for manipulating CXComments ----------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines all libclang APIs related to walking comment AST.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang-c/Index.h"
+#include "CXString.h"
+#include "CXComment.h"
+
+#include "clang/AST/CommentVisitor.h"
+
+#include "llvm/Support/ErrorHandling.h"
+
+using namespace clang;
+using namespace clang::cxstring;
+using namespace clang::comments;
+using namespace clang::cxcomment;
+
+extern "C" {
+
+enum CXCommentKind clang_Comment_getKind(CXComment CXC) {
+  const Comment *C = getASTNode(CXC);
+  if (!C)
+    return CXComment_Null;
+
+  switch (C->getCommentKind()) {
+  case Comment::NoCommentKind:
+    return CXComment_Null;
+
+  case Comment::TextCommentKind:
+    return CXComment_Text;
+
+  case Comment::InlineCommandCommentKind:
+    return CXComment_InlineCommand;
+
+  case Comment::HTMLStartTagCommentKind:
+    return CXComment_HTMLStartTag;
+
+  case Comment::HTMLEndTagCommentKind:
+    return CXComment_HTMLEndTag;
+
+  case Comment::ParagraphCommentKind:
+    return CXComment_Paragraph;
+
+  case Comment::BlockCommandCommentKind:
+    return CXComment_BlockCommand;
+
+  case Comment::ParamCommandCommentKind:
+    return CXComment_ParamCommand;
+
+  case Comment::VerbatimBlockCommentKind:
+    return CXComment_VerbatimBlockCommand;
+
+  case Comment::VerbatimBlockLineCommentKind:
+    return CXComment_VerbatimBlockLine;
+
+  case Comment::VerbatimLineCommentKind:
+    return CXComment_VerbatimLine;
+
+  case Comment::FullCommentKind:
+    return CXComment_FullComment;
+  }
+  llvm_unreachable("unknown CommentKind");
+}
+
+unsigned clang_Comment_getNumChildren(CXComment CXC) {
+  const Comment *C = getASTNode(CXC);
+  if (!C)
+    return 0;
+
+  return C->child_count();
+}
+
+CXComment clang_Comment_getChild(CXComment CXC, unsigned ChildIdx) {
+  const Comment *C = getASTNode(CXC);
+  if (!C || ChildIdx >= C->child_count())
+    return createCXComment(NULL);
+
+  return createCXComment(*(C->child_begin() + ChildIdx));
+}
+
+unsigned clang_Comment_isWhitespace(CXComment CXC) {
+  const Comment *C = getASTNode(CXC);
+  if (!C)
+    return false;
+
+  if (const TextComment *TC = dyn_cast<TextComment>(C))
+    return TC->isWhitespace();
+
+  if (const ParagraphComment *PC = dyn_cast<ParagraphComment>(C))
+    return PC->isWhitespace();
+
+  return false;
+}
+
+unsigned clang_InlineContentComment_hasTrailingNewline(CXComment CXC) {
+  const InlineContentComment *ICC = getASTNodeAs<InlineContentComment>(CXC);
+  if (!ICC)
+    return false;
+
+  return ICC->hasTrailingNewline();
+}
+
+CXString clang_TextComment_getText(CXComment CXC) {
+  const TextComment *TC = getASTNodeAs<TextComment>(CXC);
+  if (!TC)
+    return createCXString((const char *) 0);
+
+  return createCXString(TC->getText(), /*DupString=*/ false);
+}
+
+CXString clang_InlineCommandComment_getCommandName(CXComment CXC) {
+  const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC);
+  if (!ICC)
+    return createCXString((const char *) 0);
+
+  return createCXString(ICC->getCommandName(), /*DupString=*/ false);
+}
+
+unsigned clang_InlineCommandComment_getNumArgs(CXComment CXC) {
+  const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC);
+  if (!ICC)
+    return 0;
+
+  return ICC->getNumArgs();
+}
+
+CXString clang_InlineCommandComment_getArgText(CXComment CXC,
+                                               unsigned ArgIdx) {
+  const InlineCommandComment *ICC = getASTNodeAs<InlineCommandComment>(CXC);
+  if (!ICC || ArgIdx >= ICC->getNumArgs())
+    return createCXString((const char *) 0);
+
+  return createCXString(ICC->getArgText(ArgIdx), /*DupString=*/ false);
+}
+
+CXString clang_HTMLTagComment_getTagName(CXComment CXC) {
+  const HTMLTagComment *HTC = getASTNodeAs<HTMLTagComment>(CXC);
+  if (!HTC)
+    return createCXString((const char *) 0);
+
+  return createCXString(HTC->getTagName(), /*DupString=*/ false);
+}
+
+unsigned clang_HTMLStartTagComment_isSelfClosing(CXComment CXC) {
+  const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC);
+  if (!HST)
+    return false;
+
+  return HST->isSelfClosing();
+}
+
+unsigned clang_HTMLStartTag_getNumAttrs(CXComment CXC) {
+  const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC);
+  if (!HST)
+    return 0;
+
+  return HST->getNumAttrs();
+}
+
+CXString clang_HTMLStartTag_getAttrName(CXComment CXC, unsigned AttrIdx) {
+  const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC);
+  if (!HST || AttrIdx >= HST->getNumAttrs())
+    return createCXString((const char *) 0);
+
+  return createCXString(HST->getAttr(AttrIdx).Name, /*DupString=*/ false);
+}
+
+CXString clang_HTMLStartTag_getAttrValue(CXComment CXC, unsigned AttrIdx) {
+  const HTMLStartTagComment *HST = getASTNodeAs<HTMLStartTagComment>(CXC);
+  if (!HST || AttrIdx >= HST->getNumAttrs())
+    return createCXString((const char *) 0);
+
+  return createCXString(HST->getAttr(AttrIdx).Value, /*DupString=*/ false);
+}
+
+CXString clang_BlockCommandComment_getCommandName(CXComment CXC) {
+  const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
+  if (!BCC)
+    return createCXString((const char *) 0);
+
+  return createCXString(BCC->getCommandName(), /*DupString=*/ false);
+}
+
+unsigned clang_BlockCommandComment_getNumArgs(CXComment CXC) {
+  const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
+  if (!BCC)
+    return 0;
+
+  return BCC->getNumArgs();
+}
+
+CXString clang_BlockCommandComment_getArgText(CXComment CXC,
+                                              unsigned ArgIdx) {
+  const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
+  if (!BCC || ArgIdx >= BCC->getNumArgs())
+    return createCXString((const char *) 0);
+
+  return createCXString(BCC->getArgText(ArgIdx), /*DupString=*/ false);
+}
+
+CXComment clang_BlockCommandComment_getParagraph(CXComment CXC) {
+  const BlockCommandComment *BCC = getASTNodeAs<BlockCommandComment>(CXC);
+  if (!BCC)
+    return createCXComment(NULL);
+
+  return createCXComment(BCC->getParagraph());
+}
+
+CXString clang_ParamCommandComment_getParamName(CXComment CXC) {
+  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
+  if (!PCC)
+    return createCXString((const char *) 0);
+
+  return createCXString(PCC->getParamName(), /*DupString=*/ false);
+}
+
+unsigned clang_ParamCommandComment_isParamIndexValid(CXComment CXC) {
+  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
+  if (!PCC)
+    return false;
+
+  return PCC->isParamIndexValid();
+}
+
+unsigned clang_ParamCommandComment_getParamIndex(CXComment CXC) {
+  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
+  if (!PCC)
+    return ParamCommandComment::InvalidParamIndex;
+
+  return PCC->getParamIndex();
+}
+
+unsigned clang_ParamCommandComment_isDirectionExplicit(CXComment CXC) {
+  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
+  if (!PCC)
+    return false;
+
+  return PCC->isDirectionExplicit();
+}
+
+enum CXCommentParamPassDirection clang_ParamCommandComment_getDirection(
+                                                            CXComment CXC) {
+  const ParamCommandComment *PCC = getASTNodeAs<ParamCommandComment>(CXC);
+  if (!PCC)
+    return CXCommentParamPassDirection_In;
+
+  switch (PCC->getDirection()) {
+  case ParamCommandComment::In:
+    return CXCommentParamPassDirection_In;
+
+  case ParamCommandComment::Out:
+    return CXCommentParamPassDirection_Out;
+
+  case ParamCommandComment::InOut:
+    return CXCommentParamPassDirection_InOut;
+  }
+  llvm_unreachable("unknown ParamCommandComment::PassDirection");
+}
+
+CXString clang_VerbatimBlockLineComment_getText(CXComment CXC) {
+  const VerbatimBlockLineComment *VBL =
+      getASTNodeAs<VerbatimBlockLineComment>(CXC);
+  if (!VBL)
+    return createCXString((const char *) 0);
+
+  return createCXString(VBL->getText(), /*DupString=*/ false);
+}
+
+CXString clang_VerbatimLineComment_getText(CXComment CXC) {
+  const VerbatimLineComment *VLC = getASTNodeAs<VerbatimLineComment>(CXC);
+  if (!VLC)
+    return createCXString((const char *) 0);
+
+  return createCXString(VLC->getText(), /*DupString=*/ false);
+}
+
+} // end extern "C"
+
+//===----------------------------------------------------------------------===//
+// Helpers for converting comment AST to HTML.
+//===----------------------------------------------------------------------===//
+
+namespace {
+
+class ParamCommandCommentCompareIndex {
+public:
+  bool operator()(const ParamCommandComment *LHS,
+                  const ParamCommandComment *RHS) const {
+    // To sort invalid (unresolved) parameters last, this comparison relies on
+    // invalid indices to be UINT_MAX.
+    return LHS->getParamIndex() < RHS->getParamIndex();
+  }
+};
+
+class CommentASTToHTMLConverter :
+    public ConstCommentVisitor<CommentASTToHTMLConverter> {
+public:
+  CommentASTToHTMLConverter() { }
+
+  // Inline content.
+  void visitTextComment(const TextComment *C);
+  void visitInlineCommandComment(const InlineCommandComment *C);
+  void visitHTMLStartTagComment(const HTMLStartTagComment *C);
+  void visitHTMLEndTagComment(const HTMLEndTagComment *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);
+
+  // Helpers.
+
+  /// Convert a paragraph that is not a block by itself (an argument to some
+  /// command).
+  void visitNonStandaloneParagraphComment(const ParagraphComment *C);
+
+  void appendToResultWithHTMLEscaping(StringRef S);
+
+  StringRef getAsHTML() const {
+    return Result;
+  }
+
+private:
+  /// Accumulator for converted HTML.
+  std::string Result;
+};
+} // end unnamed namespace
+
+void CommentASTToHTMLConverter::visitTextComment(const TextComment *C) {
+  appendToResultWithHTMLEscaping(C->getText());
+}
+
+void CommentASTToHTMLConverter::visitInlineCommandComment(
+                                  const InlineCommandComment *C) {
+  StringRef CommandName = C->getCommandName();
+  bool HasArg0 = C->getNumArgs() > 0 && !C->getArgText(0).empty();
+  StringRef Arg0;
+  if (HasArg0)
+    Arg0 = C->getArgText(0);
+
+  if (CommandName == "b") {
+    if (!HasArg0)
+      return;
+    Result += "<b>";
+    Result += Arg0;
+    Result += "</b>";
+    return;
+  }
+  if (CommandName == "c" || CommandName == "p") {
+    if (!HasArg0)
+      return;
+    Result += "<tt>";
+    Result += Arg0;
+    Result += "</tt>";
+    return;
+  }
+  if (CommandName == "a" || CommandName == "e" || CommandName == "em") {
+    if (!HasArg0)
+      return;
+    Result += "<em>";
+    Result += Arg0;
+    Result += "</em>";
+    return;
+  }
+
+  // We don't recognize this command, so just print its arguments.
+  for (unsigned i = 0, e = C->getNumArgs(); i != e; ++i) {
+    Result += C->getArgText(i);
+    Result += " ";
+  }
+}
+
+void CommentASTToHTMLConverter::visitHTMLStartTagComment(
+                                  const HTMLStartTagComment *C) {
+  Result += "<";
+  Result += C->getTagName();
+
+  if (C->getNumAttrs() != 0) {
+    for (unsigned i = 0, e = C->getNumAttrs(); i != e; i++) {
+      Result += " ";
+      const HTMLStartTagComment::Attribute &Attr = C->getAttr(i);
+      Result += Attr.Name;
+      if (!Attr.Value.empty()) {
+        Result += "=\"";
+        Result += Attr.Value;
+        Result += " \"";
+      }
+    }
+  }
+
+  if (!C->isSelfClosing())
+    Result += ">";
+  else
+    Result += "/>";
+}
+
+void CommentASTToHTMLConverter::visitHTMLEndTagComment(
+                                  const HTMLEndTagComment *C) {
+  Result += "</";
+  Result += C->getTagName();
+  Result += ">";
+}
+
+void CommentASTToHTMLConverter::visitParagraphComment(
+                                  const ParagraphComment *C) {
+  if (C->isWhitespace())
+    return;
+
+  Result += "<p>";
+  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
+       I != E; ++I) {
+    visit(*I);
+  }
+  Result += "</p>";
+}
+
+void CommentASTToHTMLConverter::visitBlockCommandComment(
+                                  const BlockCommandComment *C) {
+  StringRef CommandName = C->getCommandName();
+  if (CommandName == "brief" || CommandName == "short") {
+    Result += "<p class=\"para-brief\">";
+    visitNonStandaloneParagraphComment(C->getParagraph());
+    Result += "</p>";
+    return;
+  }
+  if (CommandName == "returns" || CommandName == "return") {
+    Result += "<p class=\"para-returns\">";
+    Result += "<span class=\"word-returns\">Returns</span> ";
+    visitNonStandaloneParagraphComment(C->getParagraph());
+    Result += "</p>";
+    return;
+  }
+  // We don't know anything about this command.  Just render the paragraph.
+  visit(C->getParagraph());
+}
+
+void CommentASTToHTMLConverter::visitParamCommandComment(
+                                  const ParamCommandComment *C) {
+  Result += "<dt>";
+  Result += C->getParamName();
+  Result += "</dt>";
+  Result += "<dd>";
+  visitNonStandaloneParagraphComment(C->getParagraph());
+  Result += "</dd>";
+}
+
+void CommentASTToHTMLConverter::visitVerbatimBlockComment(
+                                  const VerbatimBlockComment *C) {
+  unsigned NumLines = C->getNumLines();
+  if (NumLines == 0)
+    return;
+
+  Result += "<pre>";
+  for (unsigned i = 0; i != NumLines; ++i) {
+    appendToResultWithHTMLEscaping(C->getText(i));
+    if (i + 1 != NumLines)
+      Result.append("\n");
+  }
+  Result += "</pre>";
+}
+
+void CommentASTToHTMLConverter::visitVerbatimBlockLineComment(
+                                  const VerbatimBlockLineComment *C) {
+  llvm_unreachable("should not see this AST node");
+}
+
+void CommentASTToHTMLConverter::visitVerbatimLineComment(
+                                  const VerbatimLineComment *C) {
+  Result += "<pre>";
+  appendToResultWithHTMLEscaping(C->getText());
+  Result += "</pre>";
+}
+
+void CommentASTToHTMLConverter::visitFullComment(const FullComment *C) {
+  const BlockContentComment *Brief = NULL;
+  const ParagraphComment *FirstParagraph = NULL;
+  const BlockCommandComment *Returns = NULL;
+  SmallVector<const ParamCommandComment *, 8> Params;
+  SmallVector<const BlockContentComment *, 8> MiscBlocks;
+
+  // Extract various blocks into separate variables and vectors above.
+  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
+       I != E; ++I) {
+    const Comment *Child = *I;
+    if (!Child)
+      continue;
+    switch (Child->getCommentKind()) {
+    case Comment::NoCommentKind:
+      continue;
+
+    case Comment::ParagraphCommentKind: {
+      const ParagraphComment *PC = cast<ParagraphComment>(Child);
+      if (PC->isWhitespace())
+        break;
+      if (!FirstParagraph)
+        FirstParagraph = PC;
+
+      MiscBlocks.push_back(PC);
+      break;
+    }
+
+    case Comment::BlockCommandCommentKind: {
+      const BlockCommandComment *BCC = cast<BlockCommandComment>(Child);
+      StringRef CommandName = BCC->getCommandName();
+      if (!Brief && (CommandName == "brief" || CommandName == "short")) {
+        Brief = BCC;
+        break;
+      }
+      if (!Returns && (CommandName == "returns" || CommandName == "return")) {
+        Returns = BCC;
+        break;
+      }
+      MiscBlocks.push_back(BCC);
+      break;
+    }
+
+    case Comment::ParamCommandCommentKind: {
+      const ParamCommandComment *PCC = cast<ParamCommandComment>(Child);
+      if (!PCC->hasParamName())
+        break;
+
+      if (!PCC->isDirectionExplicit() && !PCC->hasNonWhitespaceParagraph())
+        break;
+
+      Params.push_back(PCC);
+      break;
+    }
+
+    case Comment::VerbatimBlockCommentKind:
+    case Comment::VerbatimLineCommentKind:
+      MiscBlocks.push_back(cast<BlockCommandComment>(Child));
+      break;
+
+    case Comment::TextCommentKind:
+    case Comment::InlineCommandCommentKind:
+    case Comment::HTMLStartTagCommentKind:
+    case Comment::HTMLEndTagCommentKind:
+    case Comment::VerbatimBlockLineCommentKind:
+    case Comment::FullCommentKind:
+      llvm_unreachable("AST node of this kind can't be a child of "
+                       "a FullComment");
+    }
+  }
+
+  // Sort params in order they are declared in the function prototype.
+  // Unresolved parameters are put at the end of the list in the same order
+  // they were seen in the comment.
+  std::stable_sort(Params.begin(), Params.end(),
+                   ParamCommandCommentCompareIndex());
+
+  bool FirstParagraphIsBrief = false;
+  if (Brief)
+    visit(Brief);
+  else if (FirstParagraph) {
+    Result += "<p class=\"para-brief\">";
+    visitNonStandaloneParagraphComment(FirstParagraph);
+    Result += "</p>";
+    FirstParagraphIsBrief = true;
+  }
+
+  for (unsigned i = 0, e = MiscBlocks.size(); i != e; ++i) {
+    const Comment *C = MiscBlocks[i];
+    if (FirstParagraphIsBrief && C == FirstParagraph)
+      continue;
+    visit(C);
+  }
+
+  if (Params.size() != 0) {
+    Result += "<dl>";
+    for (unsigned i = 0, e = Params.size(); i != e; ++i)
+      visit(Params[i]);
+    Result += "</dl>";
+  }
+
+  if (Returns)
+    visit(Returns);
+}
+
+void CommentASTToHTMLConverter::visitNonStandaloneParagraphComment(
+                                  const ParagraphComment *C) {
+  if (!C)
+    return;
+
+  for (Comment::child_iterator I = C->child_begin(), E = C->child_end();
+       I != E; ++I) {
+    visit(*I);
+  }
+}
+
+void CommentASTToHTMLConverter::appendToResultWithHTMLEscaping(StringRef S) {
+  Result.reserve(Result.size() + S.size());
+  for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) {
+    const char C = *I;
+    switch (C) {
+      case '&':
+        Result.append("&amp;");
+        break;
+      case '<':
+        Result.append("&lt;");
+        break;
+      case '>':
+        Result.append("&gt;");
+        break;
+      case '"':
+        Result.append("&quot;");
+        break;
+      case '\'':
+        Result.append("&#39;");
+        break;
+      case '/':
+        Result.append("&#47;");
+        break;
+      default:
+        Result.push_back(C);
+        break;
+    }
+  }
+}
+
+extern "C" {
+
+CXString clang_HTMLTagComment_getAsString(CXComment CXC) {
+  const HTMLTagComment *HTC = getASTNodeAs<HTMLTagComment>(CXC);
+  if (!HTC)
+    return createCXString((const char *) 0);
+
+  CommentASTToHTMLConverter Converter;
+  Converter.visit(HTC);
+  return createCXString(Converter.getAsHTML());
+}
+
+CXString clang_FullComment_getAsHTML(CXComment CXC) {
+  const FullComment *FC = getASTNodeAs<FullComment>(CXC);
+  if (!FC)
+    return createCXString((const char *) 0);
+
+  CommentASTToHTMLConverter Converter;
+  Converter.visit(FC);
+  return createCXString(Converter.getAsHTML());
+}
+
+} // end extern "C"
+
diff --git a/tools/libclang/CXComment.h b/tools/libclang/CXComment.h
new file mode 100644
index 0000000..753877e
--- /dev/null
+++ b/tools/libclang/CXComment.h
@@ -0,0 +1,47 @@
+//===- CXComment.h - Routines for manipulating CXComments -----------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines routines for manipulating CXComments.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_CXCOMMENT_H
+#define LLVM_CLANG_CXCOMMENT_H
+
+#include "clang-c/Index.h"
+
+#include "clang/AST/Comment.h"
+
+namespace clang {
+namespace cxcomment {
+
+inline CXComment createCXComment(const comments::Comment *C) {
+  CXComment Result;
+  Result.Data = C;
+  return Result;
+}
+
+inline const comments::Comment *getASTNode(CXComment CXC) {
+  return static_cast<const comments::Comment *>(CXC.Data);
+}
+
+template<typename T>
+inline const T *getASTNodeAs(CXComment CXC) {
+  const comments::Comment *C = getASTNode(CXC);
+  if (!C)
+    return NULL;
+
+  return dyn_cast<T>(C);
+}
+
+} // end namespace cxcomment
+} // end namespace clang
+
+#endif
+
diff --git a/tools/libclang/libclang.exports b/tools/libclang/libclang.exports
index 14ded19..7d3b2a9 100644
--- a/tools/libclang/libclang.exports
+++ b/tools/libclang/libclang.exports
@@ -7,6 +7,7 @@
 clang_Cursor_getArgument
 clang_Cursor_getBriefCommentText
 clang_Cursor_getCommentRange
+clang_Cursor_getParsedComment
 clang_Cursor_getRawCommentText
 clang_Cursor_getNumArguments
 clang_Cursor_getObjCSelectorIndex
@@ -17,6 +18,33 @@
 clang_IndexAction_create
 clang_IndexAction_dispose
 clang_Range_isNull
+clang_Comment_getKind
+clang_Comment_getNumChildren
+clang_Comment_getChild
+clang_Comment_isWhitespace
+clang_InlineContentComment_hasTrailingNewline
+clang_TextComment_getText
+clang_InlineCommandComment_getCommandName
+clang_InlineCommandComment_getNumArgs
+clang_InlineCommandComment_getArgText
+clang_HTMLTagComment_getTagName
+clang_HTMLStartTagComment_isSelfClosing
+clang_HTMLStartTag_getNumAttrs
+clang_HTMLStartTag_getAttrName
+clang_HTMLStartTag_getAttrValue
+clang_BlockCommandComment_getCommandName
+clang_BlockCommandComment_getNumArgs
+clang_BlockCommandComment_getArgText
+clang_BlockCommandComment_getParagraph
+clang_ParamCommandComment_getParamName
+clang_ParamCommandComment_isParamIndexValid
+clang_ParamCommandComment_getParamIndex
+clang_ParamCommandComment_isDirectionExplicit
+clang_ParamCommandComment_getDirection
+clang_VerbatimBlockLineComment_getText
+clang_VerbatimLineComment_getText
+clang_HTMLTagComment_getAsString
+clang_FullComment_getAsHTML
 clang_annotateTokens
 clang_codeCompleteAt
 clang_codeCompleteGetContainerKind