1
0

matcher.c 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright 2010-2014 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 <stdlib.h> /* for qsort() */
  24. #include <string.h> /* for strncmp() */
  25. #include "matcher.h"
  26. #include "match.h"
  27. #include "ext.h"
  28. #include "ruby_compat.h"
  29. // order matters; we want this to be evaluated only after ruby.h
  30. #ifdef HAVE_PTHREAD_H
  31. #include <pthread.h> /* for pthread_create, pthread_join etc */
  32. #endif
  33. // comparison function for use with qsort
  34. int cmp_alpha(const void *a, const void *b)
  35. {
  36. match_t a_match = *(match_t *)a;
  37. match_t b_match = *(match_t *)b;
  38. VALUE a_str = a_match.path;
  39. VALUE b_str = b_match.path;
  40. char *a_p = RSTRING_PTR(a_str);
  41. long a_len = RSTRING_LEN(a_str);
  42. char *b_p = RSTRING_PTR(b_str);
  43. long b_len = RSTRING_LEN(b_str);
  44. int order = 0;
  45. if (a_len > b_len) {
  46. order = strncmp(a_p, b_p, b_len);
  47. if (order == 0)
  48. order = 1; // shorter string (b) wins
  49. } else if (a_len < b_len) {
  50. order = strncmp(a_p, b_p, a_len);
  51. if (order == 0)
  52. order = -1; // shorter string (a) wins
  53. } else {
  54. order = strncmp(a_p, b_p, a_len);
  55. }
  56. return order;
  57. }
  58. // comparison function for use with qsort
  59. int cmp_score(const void *a, const void *b)
  60. {
  61. match_t a_match = *(match_t *)a;
  62. match_t b_match = *(match_t *)b;
  63. if (a_match.score > b_match.score)
  64. return -1; // a scores higher, a should appear sooner
  65. else if (a_match.score < b_match.score)
  66. return 1; // b scores higher, a should appear later
  67. else
  68. return cmp_alpha(a, b);
  69. }
  70. VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
  71. {
  72. VALUE always_show_dot_files;
  73. VALUE never_show_dot_files;
  74. VALUE options;
  75. VALUE scanner;
  76. // process arguments: 1 mandatory, 1 optional
  77. if (rb_scan_args(argc, argv, "11", &scanner, &options) == 1)
  78. options = Qnil;
  79. if (NIL_P(scanner))
  80. rb_raise(rb_eArgError, "nil scanner");
  81. rb_iv_set(self, "@scanner", scanner);
  82. // check optional options hash for overrides
  83. always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
  84. never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
  85. rb_iv_set(self, "@always_show_dot_files", always_show_dot_files);
  86. rb_iv_set(self, "@never_show_dot_files", never_show_dot_files);
  87. return Qnil;
  88. }
  89. typedef struct {
  90. int thread_count;
  91. int thread_index;
  92. match_t *matches;
  93. long path_count;
  94. VALUE paths;
  95. VALUE abbrev;
  96. VALUE always_show_dot_files;
  97. VALUE never_show_dot_files;
  98. } thread_args_t;
  99. void *match_thread(void *thread_args)
  100. {
  101. long i;
  102. thread_args_t *args = (thread_args_t *)thread_args;
  103. for (i = args->thread_index; i < args->path_count; i += args->thread_count) {
  104. VALUE path = RARRAY_PTR(args->paths)[i];
  105. calculate_match(path,
  106. args->abbrev,
  107. args->always_show_dot_files,
  108. args->never_show_dot_files,
  109. &args->matches[i]);
  110. }
  111. return NULL;
  112. }
  113. VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
  114. {
  115. long i, limit, path_count, thread_count;
  116. #ifdef HAVE_PTHREAD_H
  117. long err;
  118. #endif
  119. match_t *matches;
  120. thread_args_t *thread_args;
  121. VALUE abbrev;
  122. VALUE always_show_dot_files;
  123. VALUE limit_option;
  124. VALUE never_show_dot_files;
  125. VALUE options;
  126. VALUE paths;
  127. VALUE results;
  128. VALUE scanner;
  129. VALUE threads_option;
  130. // process arguments: 1 mandatory, 1 optional
  131. if (rb_scan_args(argc, argv, "11", &abbrev, &options) == 1)
  132. options = Qnil;
  133. if (NIL_P(abbrev))
  134. rb_raise(rb_eArgError, "nil abbrev");
  135. abbrev = StringValue(abbrev);
  136. abbrev = rb_funcall(abbrev, rb_intern("downcase"), 0);
  137. // check optional options has for overrides
  138. limit_option = CommandT_option_from_hash("limit", options);
  139. threads_option = CommandT_option_from_hash("threads", options);
  140. // get unsorted matches
  141. scanner = rb_iv_get(self, "@scanner");
  142. paths = rb_funcall(scanner, rb_intern("paths"), 0);
  143. always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
  144. never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
  145. path_count = RARRAY_LEN(paths);
  146. matches = malloc(path_count * sizeof(match_t));
  147. if (!matches)
  148. rb_raise(rb_eNoMemError, "memory allocation failed");
  149. thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
  150. #ifdef HAVE_PTHREAD_H
  151. #define THREAD_THRESHOLD 1000 /* avoid the overhead of threading when search space is small */
  152. if (path_count < THREAD_THRESHOLD)
  153. thread_count = 1;
  154. pthread_t *threads = malloc(sizeof(pthread_t) * thread_count);
  155. if (!threads)
  156. rb_raise(rb_eNoMemError, "memory allocation failed");
  157. #endif
  158. thread_args = malloc(sizeof(thread_args_t) * thread_count);
  159. if (!thread_args)
  160. rb_raise(rb_eNoMemError, "memory allocation failed");
  161. for (i = 0; i < thread_count; i++) {
  162. thread_args[i].thread_count = thread_count;
  163. thread_args[i].thread_index = i;
  164. thread_args[i].matches = matches;
  165. thread_args[i].path_count = path_count;
  166. thread_args[i].paths = paths;
  167. thread_args[i].abbrev = abbrev;
  168. thread_args[i].always_show_dot_files = always_show_dot_files;
  169. thread_args[i].never_show_dot_files = never_show_dot_files;
  170. #ifdef HAVE_PTHREAD_H
  171. if (i == thread_count - 1) {
  172. #endif
  173. // for the last "worker", we'll just use the main thread
  174. (void)match_thread(&thread_args[i]);
  175. #ifdef HAVE_PTHREAD_H
  176. } else {
  177. err = pthread_create(&threads[i], NULL, match_thread, (void *)&thread_args[i]);
  178. if (err != 0)
  179. rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", (int)err);
  180. }
  181. #endif
  182. }
  183. #ifdef HAVE_PTHREAD_H
  184. for (i = 0; i < thread_count - 1; i++) {
  185. err = pthread_join(threads[i], NULL);
  186. if (err != 0)
  187. rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", (int)err);
  188. }
  189. free(threads);
  190. #endif
  191. if (RSTRING_LEN(abbrev) == 0 ||
  192. (RSTRING_LEN(abbrev) == 1 && RSTRING_PTR(abbrev)[0] == '.'))
  193. // alphabetic order if search string is only "" or "."
  194. qsort(matches, path_count, sizeof(match_t), cmp_alpha);
  195. else
  196. // for all other non-empty search strings, sort by score
  197. qsort(matches, path_count, sizeof(match_t), cmp_score);
  198. results = rb_ary_new();
  199. limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
  200. if (limit == 0)
  201. limit = path_count;
  202. for (i = 0; i < path_count && limit > 0; i++) {
  203. if (matches[i].score > 0.0) {
  204. rb_funcall(results, rb_intern("push"), 1, matches[i].path);
  205. limit--;
  206. }
  207. }
  208. free(matches);
  209. return results;
  210. }