1
0

match.c 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Copyright 2010-2013 Wincent Colaiuta. All rights reserved.
  2. //
  3. // Redistribution and use in source and binary forms, with or without
  4. // modification, are permitted provided that the following conditions are met:
  5. //
  6. // 1. Redistributions of source code must retain the above copyright notice,
  7. // this list of conditions and the following disclaimer.
  8. // 2. Redistributions in binary form must reproduce the above copyright notice,
  9. // this list of conditions and the following disclaimer in the documentation
  10. // and/or other materials provided with the distribution.
  11. //
  12. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  13. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  14. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  15. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
  16. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  17. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  18. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  19. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  20. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  21. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  22. // POSSIBILITY OF SUCH DAMAGE.
  23. #include <float.h> /* for DBL_MAX */
  24. #include "match.h"
  25. #include "ext.h"
  26. #include "ruby_compat.h"
  27. // use a struct to make passing params during recursion easier
  28. typedef struct {
  29. char *haystack_p; // pointer to the path string to be searched
  30. long haystack_len; // length of same
  31. char *needle_p; // pointer to search string (needle)
  32. long needle_len; // length of same
  33. double max_score_per_char;
  34. int dot_file; // boolean: true if str is a dot-file
  35. int always_show_dot_files; // boolean
  36. int never_show_dot_files; // boolean
  37. double *memo; // memoization
  38. } matchinfo_t;
  39. double recursive_match(matchinfo_t *m, // sharable meta-data
  40. long haystack_idx, // where in the path string to start
  41. long needle_idx, // where in the needle string to start
  42. long last_idx, // location of last matched character
  43. double score) // cumulative score so far
  44. {
  45. double seen_score = 0; // remember best score seen via recursion
  46. int dot_file_match = 0; // true if needle matches a dot-file
  47. int dot_search = 0; // true if searching for a dot
  48. long i, j, distance;
  49. int found;
  50. double score_for_char;
  51. // do we have a memoized result we can return?
  52. double memoized = m->memo[needle_idx * m->needle_len + haystack_idx];
  53. if (memoized != DBL_MAX)
  54. return memoized;
  55. // bail early if not enough room (left) in haystack for (rest of) needle
  56. if (m->haystack_len - haystack_idx < m->needle_len - needle_idx) {
  57. score = 0.0;
  58. goto memoize;
  59. }
  60. for (i = needle_idx; i < m->needle_len; i++) {
  61. char c = m->needle_p[i];
  62. if (c == '.')
  63. dot_search = 1;
  64. found = 0;
  65. // similar to above, we'll stop iterating when we know we're too close
  66. // to the end of the string to possibly match
  67. for (j = haystack_idx;
  68. j <= m->haystack_len - (m->needle_len - i);
  69. j++, haystack_idx++) {
  70. char d = m->haystack_p[j];
  71. if (d == '.') {
  72. if (j == 0 || m->haystack_p[j - 1] == '/') {
  73. m->dot_file = 1; // this is a dot-file
  74. if (dot_search) // and we are searching for a dot
  75. dot_file_match = 1; // so this must be a match
  76. }
  77. } else if (d >= 'A' && d <= 'Z') {
  78. d += 'a' - 'A'; // add 32 to downcase
  79. }
  80. if (c == d) {
  81. found = 1;
  82. dot_search = 0;
  83. // calculate score
  84. score_for_char = m->max_score_per_char;
  85. distance = j - last_idx;
  86. if (distance > 1) {
  87. double factor = 1.0;
  88. char last = m->haystack_p[j - 1];
  89. char curr = m->haystack_p[j]; // case matters, so get again
  90. if (last == '/')
  91. factor = 0.9;
  92. else if (last == '-' ||
  93. last == '_' ||
  94. last == ' ' ||
  95. (last >= '0' && last <= '9'))
  96. factor = 0.8;
  97. else if (last >= 'a' && last <= 'z' &&
  98. curr >= 'A' && curr <= 'Z')
  99. factor = 0.8;
  100. else if (last == '.')
  101. factor = 0.7;
  102. else
  103. // if no "special" chars behind char, factor diminishes
  104. // as distance from last matched char increases
  105. factor = (1.0 / distance) * 0.75;
  106. score_for_char *= factor;
  107. }
  108. if (++j < m->haystack_len) {
  109. // bump cursor one char to the right and
  110. // use recursion to try and find a better match
  111. double sub_score = recursive_match(m, j, i, last_idx, score);
  112. if (sub_score > seen_score)
  113. seen_score = sub_score;
  114. }
  115. score += score_for_char;
  116. last_idx = haystack_idx + 1;
  117. break;
  118. }
  119. }
  120. if (!found) {
  121. score = 0.0;
  122. goto memoize;
  123. }
  124. }
  125. if (m->dot_file &&
  126. (m->never_show_dot_files ||
  127. (!dot_file_match && !m->always_show_dot_files))) {
  128. score = 0.0;
  129. goto memoize;
  130. }
  131. score = score > seen_score ? score : seen_score;
  132. memoize:
  133. m->memo[needle_idx * m->needle_len + haystack_idx] = score;
  134. return score;
  135. }
  136. void calculate_match(VALUE str,
  137. VALUE needle,
  138. VALUE always_show_dot_files,
  139. VALUE never_show_dot_files,
  140. match_t *out)
  141. {
  142. long i, max;
  143. double score;
  144. matchinfo_t m;
  145. m.haystack_p = RSTRING_PTR(str);
  146. m.haystack_len = RSTRING_LEN(str);
  147. m.needle_p = RSTRING_PTR(needle);
  148. m.needle_len = RSTRING_LEN(needle);
  149. m.max_score_per_char = (1.0 / m.haystack_len + 1.0 / m.needle_len) / 2;
  150. m.dot_file = 0;
  151. m.always_show_dot_files = always_show_dot_files == Qtrue;
  152. m.never_show_dot_files = never_show_dot_files == Qtrue;
  153. // calculate score
  154. score = 1.0;
  155. // special case for zero-length search string
  156. if (m.needle_len == 0) {
  157. // filter out dot files
  158. if (!m.always_show_dot_files) {
  159. for (i = 0; i < m.haystack_len; i++) {
  160. char c = m.haystack_p[i];
  161. if (c == '.' && (i == 0 || m.haystack_p[i - 1] == '/')) {
  162. score = 0.0;
  163. break;
  164. }
  165. }
  166. }
  167. } else if (m.haystack_len > 0) { // normal case
  168. // prepare for memoization
  169. double memo[m.haystack_len * m.needle_len];
  170. for (i = 0, max = m.haystack_len * m.needle_len; i < max; i++)
  171. memo[i] = DBL_MAX;
  172. m.memo = memo;
  173. score = recursive_match(&m, 0, 0, 0, 0.0);
  174. }
  175. // final book-keeping
  176. out->path = str;
  177. out->score = score;
  178. }