Reland "Implement a (simple) Markdown generator"

Relanding with fixes to tests for the failing bots.

Differential Revision: https://reviews.llvm.org/D43424

llvm-svn: 341955
diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp
new file mode 100644
index 0000000..f6c173c
--- /dev/null
+++ b/clang-tools-extra/clang-doc/MDGenerator.cpp
@@ -0,0 +1,312 @@
+//===-- MDGenerator.cpp - Markdown Generator --------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Generators.h"
+#include "Representation.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include <string>
+
+using namespace llvm;
+
+namespace clang {
+namespace doc {
+
+// Enum conversion
+
+std::string getAccess(AccessSpecifier AS) {
+  switch (AS) {
+  case AccessSpecifier::AS_public:
+    return "public";
+  case AccessSpecifier::AS_protected:
+    return "protected";
+  case AccessSpecifier::AS_private:
+    return "private";
+  case AccessSpecifier::AS_none:
+    return {};
+  }
+}
+
+std::string getTagType(TagTypeKind AS) {
+  switch (AS) {
+  case TagTypeKind::TTK_Class:
+    return "class";
+  case TagTypeKind::TTK_Union:
+    return "union";
+  case TagTypeKind::TTK_Interface:
+    return "interface";
+  case TagTypeKind::TTK_Struct:
+    return "struct";
+  case TagTypeKind::TTK_Enum:
+    return "enum";
+  }
+}
+
+// Markdown generation
+
+std::string genItalic(const Twine &Text) { return "*" + Text.str() + "*"; }
+
+std::string genEmphasis(const Twine &Text) { return "**" + Text.str() + "**"; }
+
+std::string genLink(const Twine &Text, const Twine &Link) {
+  return "[" + Text.str() + "](" + Link.str() + ")";
+}
+
+std::string genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) {
+  std::string Buffer;
+  llvm::raw_string_ostream Stream(Buffer);
+  bool First = true;
+  for (const auto &R : Refs) {
+    if (!First)
+      Stream << ", ";
+    Stream << R.Name;
+    First = false;
+  }
+  return Stream.str();
+}
+
+void writeLine(const Twine &Text, raw_ostream &OS) { OS << Text << "\n"; }
+
+void writeNewLine(raw_ostream &OS) { OS << "\n"; }
+
+void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
+  OS << std::string(Num, '#') + " " + Text << "\n";
+}
+
+void writeFileDefinition(const Location &L, raw_ostream &OS) {
+  OS << genItalic("Defined at line " + std::to_string(L.LineNumber) + " of " +
+                  L.Filename)
+     << "\n";
+}
+
+void writeDescription(const CommentInfo &I, raw_ostream &OS) {
+  if (I.Kind == "FullComment") {
+    for (const auto &Child : I.Children)
+      writeDescription(*Child, OS);
+  } else if (I.Kind == "ParagraphComment") {
+    for (const auto &Child : I.Children)
+      writeDescription(*Child, OS);
+    writeNewLine(OS);
+  } else if (I.Kind == "BlockCommandComment") {
+    OS << genEmphasis(I.Name);
+    for (const auto &Child : I.Children)
+      writeDescription(*Child, OS);
+  } else if (I.Kind == "InlineCommandComment") {
+    OS << genEmphasis(I.Name) << " " << I.Text;
+  } else if (I.Kind == "ParamCommandComment") {
+    std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
+    OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n";
+  } else if (I.Kind == "TParamCommandComment") {
+    std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
+    OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n";
+  } else if (I.Kind == "VerbatimBlockComment") {
+    for (const auto &Child : I.Children)
+      writeDescription(*Child, OS);
+  } else if (I.Kind == "VerbatimBlockLineComment") {
+    OS << I.Text;
+    writeNewLine(OS);
+  } else if (I.Kind == "VerbatimLineComment") {
+    OS << I.Text;
+    writeNewLine(OS);
+  } else if (I.Kind == "HTMLStartTagComment") {
+    if (I.AttrKeys.size() != I.AttrValues.size())
+      return;
+    std::string Buffer;
+    llvm::raw_string_ostream Attrs(Buffer);
+    for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
+      Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
+
+    std::string CloseTag = I.SelfClosing ? "/>" : ">";
+    writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
+  } else if (I.Kind == "HTMLEndTagComment") {
+    writeLine("</" + I.Name + ">", OS);
+  } else if (I.Kind == "TextComment") {
+    OS << I.Text;
+  } else {
+    OS << "Unknown comment kind: " << I.Kind << ".\n";
+  }
+}
+
+void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) {
+  if (I.Scoped)
+    writeLine("| enum class " + I.Name + " |", OS);
+  else
+    writeLine("| enum " + I.Name + " |", OS);
+  writeLine("--", OS);
+
+  std::string Buffer;
+  llvm::raw_string_ostream Members(Buffer);
+  if (!I.Members.empty())
+    for (const auto &N : I.Members)
+      Members << "| " << N << " |\n";
+  writeLine(Members.str(), OS);
+  if (I.DefLoc)
+    writeFileDefinition(I.DefLoc.getValue(), OS);
+
+  for (const auto &C : I.Description)
+    writeDescription(C, OS);
+}
+
+void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) {
+  std::string Buffer;
+  llvm::raw_string_ostream Stream(Buffer);
+  bool First = true;
+  for (const auto &N : I.Params) {
+    if (!First)
+      Stream << ", ";
+    Stream << N.Type.Name + " " + N.Name;
+    First = false;
+  }
+  std::string Access = getAccess(I.Access);
+  if (Access != "")
+    writeHeader(genItalic(Access) + " " + I.ReturnType.Type.Name + " " + I.Name + "(" + Stream.str() + ")", 3, OS);
+  else 
+    writeHeader(I.ReturnType.Type.Name + " " + I.Name + "(" + Stream.str() + ")", 3, OS);
+  if (I.DefLoc)
+    writeFileDefinition(I.DefLoc.getValue(), OS);
+
+  for (const auto &C : I.Description)
+    writeDescription(C, OS);
+}
+
+void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) {
+  if (I.Name == "")
+    writeHeader("Global Namespace", 1, OS);
+  else
+    writeHeader("namespace " + I.Name, 1, OS);
+  writeNewLine(OS);
+
+  if (!I.Description.empty()) {
+    for (const auto &C : I.Description)
+      writeDescription(C, OS);
+    writeNewLine(OS);
+  }
+
+  if (!I.ChildNamespaces.empty()) {
+    writeHeader("Namespaces", 2, OS);
+    for (const auto &R : I.ChildNamespaces)
+      writeLine(R.Name, OS);
+    writeNewLine(OS);
+  }
+  if (!I.ChildRecords.empty()) {
+    writeHeader("Records", 2, OS);
+    for (const auto &R : I.ChildRecords)
+      writeLine(R.Name, OS);
+    writeNewLine(OS);
+  }
+  if (!I.ChildFunctions.empty()) {
+    writeHeader("Functions", 2, OS);
+    for (const auto &F : I.ChildFunctions)
+      genMarkdown(F, OS);
+    writeNewLine(OS);
+  }
+  if (!I.ChildEnums.empty()) {
+    writeHeader("Enums", 2, OS);
+    for (const auto &E : I.ChildEnums)
+      genMarkdown(E, OS);
+    writeNewLine(OS);
+  }
+}
+
+void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) {
+  writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
+  if (I.DefLoc)
+    writeFileDefinition(I.DefLoc.getValue(), OS);
+
+  if (!I.Description.empty()) {
+    for (const auto &C : I.Description)
+      writeDescription(C, OS);
+    writeNewLine(OS);
+  }
+
+  std::string Parents = genReferenceList(I.Parents);
+  std::string VParents = genReferenceList(I.VirtualParents);
+  if (!Parents.empty() || !VParents.empty()) {
+    if (Parents.empty())
+      writeLine("Inherits from " + VParents, OS);
+    else if (VParents.empty())
+      writeLine("Inherits from " + Parents, OS);
+    else
+      writeLine("Inherits from " + Parents + ", " + VParents, OS);
+    writeNewLine(OS);
+  }
+
+  if (!I.Members.empty()) {
+    writeHeader("Members", 2, OS);
+    for (const auto Member : I.Members) {
+      std::string Access = getAccess(Member.Access);
+      if (Access != "")
+        writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS);
+      else
+        writeLine(Member.Type.Name + " " + Member.Name, OS);
+    }
+    writeNewLine(OS);
+  }
+
+  if (!I.ChildRecords.empty()) {
+    writeHeader("Records", 2, OS);
+    for (const auto &R : I.ChildRecords)
+      writeLine(R.Name, OS);
+    writeNewLine(OS);
+  }
+  if (!I.ChildFunctions.empty()) {
+    writeHeader("Functions", 2, OS);
+    for (const auto &F : I.ChildFunctions)
+      genMarkdown(F, OS);
+    writeNewLine(OS);
+  }
+  if (!I.ChildEnums.empty()) {
+    writeHeader("Enums", 2, OS);
+    for (const auto &E : I.ChildEnums)
+      genMarkdown(E, OS);
+    writeNewLine(OS);
+  }
+}
+
+/// Generator for Markdown documentation.
+class MDGenerator : public Generator {
+public:
+  static const char *Format;
+
+  llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS) override;
+};
+
+const char *MDGenerator::Format = "md";
+
+llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) {
+  switch (I->IT) {
+  case InfoType::IT_namespace:
+    genMarkdown(*static_cast<clang::doc::NamespaceInfo *>(I), OS);
+    break;
+  case InfoType::IT_record:
+    genMarkdown(*static_cast<clang::doc::RecordInfo *>(I), OS);
+    break;
+  case InfoType::IT_enum:
+    genMarkdown(*static_cast<clang::doc::EnumInfo *>(I), OS);
+    break;
+  case InfoType::IT_function:
+    genMarkdown(*static_cast<clang::doc::FunctionInfo *>(I), OS);
+    break;
+  case InfoType::IT_default:
+    return llvm::make_error<llvm::StringError>("Unexpected info type.\n",
+                                               llvm::inconvertibleErrorCode());
+  }
+  return llvm::Error::success();
+}
+
+static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
+                                              "Generator for MD output.");
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register the generator.
+volatile int MDGeneratorAnchorSource = 0;
+
+} // namespace doc
+} // namespace clang