VirtualFileSystem: Add YAMLVFSWriter to generate VFS mapping files

This moves the logic to write a JSON VFS mapping from the C api into
VirtualFileSystem, so that we can use it internally.

No functional change.

llvm-svn: 209241
diff --git a/clang/lib/Basic/VirtualFileSystem.cpp b/clang/lib/Basic/VirtualFileSystem.cpp
index fae6a35..24454b0 100644
--- a/clang/lib/Basic/VirtualFileSystem.cpp
+++ b/clang/lib/Basic/VirtualFileSystem.cpp
@@ -11,6 +11,7 @@
 
 #include "clang/Basic/VirtualFileSystem.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/iterator_range.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/MemoryBuffer.h"
@@ -843,3 +844,118 @@
   // dev_t value from the OS.
   return UniqueID(std::numeric_limits<uint64_t>::max(), ID);
 }
+
+#ifndef NDEBUG
+static bool pathHasTraversal(StringRef Path) {
+  using namespace llvm::sys;
+  for (StringRef Comp : llvm::make_range(path::begin(Path), path::end(Path)))
+    if (Comp == "." || Comp == "..")
+      return true;
+  return false;
+}
+#endif
+
+void YAMLVFSWriter::addFileMapping(StringRef VirtualPath, StringRef RealPath) {
+  assert(sys::path::is_absolute(VirtualPath) && "virtual path not absolute");
+  assert(sys::path::is_absolute(RealPath) && "real path not absolute");
+  assert(!pathHasTraversal(VirtualPath) && "path traversal is not supported");
+  Mappings.emplace_back(VirtualPath, RealPath);
+}
+
+ArrayRef<YAMLVFSWriter::MapEntry>
+YAMLVFSWriter::printDirNodes(llvm::raw_ostream &OS, ArrayRef<MapEntry> Entries,
+                             StringRef ParentPath, unsigned Indent) {
+  while (!Entries.empty()) {
+    const MapEntry &Entry = Entries.front();
+    OS.indent(Indent) << "{\n";
+    Indent += 2;
+    OS.indent(Indent) << "'type': 'directory',\n";
+    StringRef DirName =
+        containedPart(ParentPath, sys::path::parent_path(Entry.VPath));
+    OS.indent(Indent)
+        << "'name': \"" << llvm::yaml::escape(DirName) << "\",\n";
+    OS.indent(Indent) << "'contents': [\n";
+    Entries = printContents(OS, Entries, Indent + 2);
+    OS.indent(Indent) << "]\n";
+    Indent -= 2;
+    OS.indent(Indent) << '}';
+    if (Entries.empty()) {
+      OS << '\n';
+      break;
+    }
+    StringRef NextVPath = Entries.front().VPath;
+    if (!containedIn(ParentPath, NextVPath)) {
+      OS << '\n';
+      break;
+    }
+    OS << ",\n";
+  }
+  return Entries;
+}
+
+ArrayRef<YAMLVFSWriter::MapEntry>
+YAMLVFSWriter::printContents(llvm::raw_ostream &OS, ArrayRef<MapEntry> Entries,
+                             unsigned Indent) {
+  using namespace llvm::sys;
+  while (!Entries.empty()) {
+    const MapEntry &Entry = Entries.front();
+    Entries = Entries.slice(1);
+    StringRef ParentPath = path::parent_path(Entry.VPath);
+    StringRef VName = path::filename(Entry.VPath);
+    OS.indent(Indent) << "{\n";
+    Indent += 2;
+    OS.indent(Indent) << "'type': 'file',\n";
+    OS.indent(Indent) << "'name': \"" << llvm::yaml::escape(VName) << "\",\n";
+    OS.indent(Indent) << "'external-contents': \""
+                      << llvm::yaml::escape(Entry.RPath) << "\"\n";
+    Indent -= 2;
+    OS.indent(Indent) << '}';
+    if (Entries.empty()) {
+      OS << '\n';
+      break;
+    }
+    StringRef NextVPath = Entries.front().VPath;
+    if (!containedIn(ParentPath, NextVPath)) {
+      OS << '\n';
+      break;
+    }
+    OS << ",\n";
+    if (path::parent_path(NextVPath) != ParentPath) {
+      Entries = printDirNodes(OS, Entries, ParentPath, Indent);
+    }
+  }
+  return Entries;
+}
+
+bool YAMLVFSWriter::containedIn(StringRef Parent, StringRef Path) {
+  return Path.startswith(Parent);
+}
+
+StringRef YAMLVFSWriter::containedPart(StringRef Parent, StringRef Path) {
+  assert(containedIn(Parent, Path));
+  if (Parent.empty())
+    return Path;
+  return Path.slice(Parent.size() + 1, StringRef::npos);
+}
+
+void YAMLVFSWriter::write(llvm::raw_ostream &OS) {
+  std::sort(Mappings.begin(), Mappings.end(),
+            [](const MapEntry &LHS, const MapEntry &RHS) {
+    return LHS.VPath < RHS.VPath;
+  });
+
+  OS << "{\n"
+        "  'version': 0,\n";
+  if (IsCaseSensitive.hasValue()) {
+    OS << "  'case-sensitive': '";
+    if (IsCaseSensitive.getValue())
+      OS << "true";
+    else
+      OS << "false";
+    OS << "',\n";
+  }
+  OS << "  'roots': [\n";
+  printDirNodes(OS, Mappings, "", 4);
+  OS << "  ]\n"
+     << "}\n";
+}