FileMatchTrie.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. //===--- FileMatchTrie.cpp - ----------------------------------------------===//
  2. //
  3. // The LLVM Compiler Infrastructure
  4. //
  5. // This file is distributed under the University of Illinois Open Source
  6. // License. See LICENSE.TXT for details.
  7. //
  8. //===----------------------------------------------------------------------===//
  9. //
  10. // This file contains the implementation of a FileMatchTrie.
  11. //
  12. //===----------------------------------------------------------------------===//
  13. #include "clang/Tooling/FileMatchTrie.h"
  14. #include "llvm/ADT/StringMap.h"
  15. #include "llvm/Support/FileSystem.h"
  16. #include "llvm/Support/Path.h"
  17. #include "llvm/Support/raw_ostream.h"
  18. #include <sstream>
  19. using namespace clang;
  20. using namespace tooling;
  21. namespace {
  22. /// \brief Default \c PathComparator using \c llvm::sys::fs::equivalent().
  23. struct DefaultPathComparator : public PathComparator {
  24. bool equivalent(StringRef FileA, StringRef FileB) const override {
  25. return FileA == FileB || llvm::sys::fs::equivalent(FileA, FileB);
  26. }
  27. };
  28. }
  29. namespace clang {
  30. namespace tooling {
  31. /// \brief A node of the \c FileMatchTrie.
  32. ///
  33. /// Each node has storage for up to one path and a map mapping a path segment to
  34. /// child nodes. The trie starts with an empty root node.
  35. class FileMatchTrieNode {
  36. public:
  37. /// \brief Inserts 'NewPath' into this trie. \c ConsumedLength denotes
  38. /// the number of \c NewPath's trailing characters already consumed during
  39. /// recursion.
  40. ///
  41. /// An insert of a path
  42. /// 'p'starts at the root node and does the following:
  43. /// - If the node is empty, insert 'p' into its storage and abort.
  44. /// - If the node has a path 'p2' but no children, take the last path segment
  45. /// 's' of 'p2', put a new child into the map at 's' an insert the rest of
  46. /// 'p2' there.
  47. /// - Insert a new child for the last segment of 'p' and insert the rest of
  48. /// 'p' there.
  49. ///
  50. /// An insert operation is linear in the number of a path's segments.
  51. void insert(StringRef NewPath, unsigned ConsumedLength = 0) {
  52. // We cannot put relative paths into the FileMatchTrie as then a path can be
  53. // a postfix of another path, violating a core assumption of the trie.
  54. if (llvm::sys::path::is_relative(NewPath))
  55. return;
  56. if (Path.empty()) {
  57. // This is an empty leaf. Store NewPath and return.
  58. Path = NewPath;
  59. return;
  60. }
  61. if (Children.empty()) {
  62. // This is a leaf, ignore duplicate entry if 'Path' equals 'NewPath'.
  63. if (NewPath == Path)
  64. return;
  65. // Make this a node and create a child-leaf with 'Path'.
  66. StringRef Element(llvm::sys::path::filename(
  67. StringRef(Path).drop_back(ConsumedLength)));
  68. Children[Element].Path = Path;
  69. }
  70. StringRef Element(llvm::sys::path::filename(
  71. StringRef(NewPath).drop_back(ConsumedLength)));
  72. Children[Element].insert(NewPath, ConsumedLength + Element.size() + 1);
  73. }
  74. /// \brief Tries to find the node under this \c FileMatchTrieNode that best
  75. /// matches 'FileName'.
  76. ///
  77. /// If multiple paths fit 'FileName' equally well, \c IsAmbiguous is set to
  78. /// \c true and an empty string is returned. If no path fits 'FileName', an
  79. /// empty string is returned. \c ConsumedLength denotes the number of
  80. /// \c Filename's trailing characters already consumed during recursion.
  81. ///
  82. /// To find the best matching node for a given path 'p', the
  83. /// \c findEquivalent() function is called recursively for each path segment
  84. /// (back to fron) of 'p' until a node 'n' is reached that does not ..
  85. /// - .. have children. In this case it is checked
  86. /// whether the stored path is equivalent to 'p'. If yes, the best match is
  87. /// found. Otherwise continue with the parent node as if this node did not
  88. /// exist.
  89. /// - .. a child matching the next path segment. In this case, all children of
  90. /// 'n' are an equally good match for 'p'. All children are of 'n' are found
  91. /// recursively and their equivalence to 'p' is determined. If none are
  92. /// equivalent, continue with the parent node as if 'n' didn't exist. If one
  93. /// is equivalent, the best match is found. Otherwise, report and ambigiuity
  94. /// error.
  95. StringRef findEquivalent(const PathComparator& Comparator,
  96. StringRef FileName,
  97. bool &IsAmbiguous,
  98. unsigned ConsumedLength = 0) const {
  99. if (Children.empty()) {
  100. if (Comparator.equivalent(StringRef(Path), FileName))
  101. return StringRef(Path);
  102. return StringRef();
  103. }
  104. StringRef Element(llvm::sys::path::filename(FileName.drop_back(
  105. ConsumedLength)));
  106. llvm::StringMap<FileMatchTrieNode>::const_iterator MatchingChild =
  107. Children.find(Element);
  108. if (MatchingChild != Children.end()) {
  109. StringRef Result = MatchingChild->getValue().findEquivalent(
  110. Comparator, FileName, IsAmbiguous,
  111. ConsumedLength + Element.size() + 1);
  112. if (!Result.empty() || IsAmbiguous)
  113. return Result;
  114. }
  115. std::vector<StringRef> AllChildren;
  116. getAll(AllChildren, MatchingChild);
  117. StringRef Result;
  118. for (unsigned i = 0; i < AllChildren.size(); i++) {
  119. if (Comparator.equivalent(AllChildren[i], FileName)) {
  120. if (Result.empty()) {
  121. Result = AllChildren[i];
  122. } else {
  123. IsAmbiguous = true;
  124. return StringRef();
  125. }
  126. }
  127. }
  128. return Result;
  129. }
  130. private:
  131. /// \brief Gets all paths under this FileMatchTrieNode.
  132. void getAll(std::vector<StringRef> &Results,
  133. llvm::StringMap<FileMatchTrieNode>::const_iterator Except) const {
  134. if (Path.empty())
  135. return;
  136. if (Children.empty()) {
  137. Results.push_back(StringRef(Path));
  138. return;
  139. }
  140. for (llvm::StringMap<FileMatchTrieNode>::const_iterator
  141. It = Children.begin(), E = Children.end();
  142. It != E; ++It) {
  143. if (It == Except)
  144. continue;
  145. It->getValue().getAll(Results, Children.end());
  146. }
  147. }
  148. // The stored absolute path in this node. Only valid for leaf nodes, i.e.
  149. // nodes where Children.empty().
  150. std::string Path;
  151. // The children of this node stored in a map based on the next path segment.
  152. llvm::StringMap<FileMatchTrieNode> Children;
  153. };
  154. } // end namespace tooling
  155. } // end namespace clang
  156. FileMatchTrie::FileMatchTrie()
  157. : Root(new FileMatchTrieNode), Comparator(new DefaultPathComparator()) {}
  158. FileMatchTrie::FileMatchTrie(PathComparator *Comparator)
  159. : Root(new FileMatchTrieNode), Comparator(Comparator) {}
  160. FileMatchTrie::~FileMatchTrie() {
  161. delete Root;
  162. }
  163. void FileMatchTrie::insert(StringRef NewPath) {
  164. Root->insert(NewPath);
  165. }
  166. StringRef FileMatchTrie::findEquivalent(StringRef FileName,
  167. raw_ostream &Error) const {
  168. if (llvm::sys::path::is_relative(FileName)) {
  169. Error << "Cannot resolve relative paths";
  170. return StringRef();
  171. }
  172. bool IsAmbiguous = false;
  173. StringRef Result = Root->findEquivalent(*Comparator, FileName, IsAmbiguous);
  174. if (IsAmbiguous)
  175. Error << "Path is ambiguous";
  176. return Result;
  177. }