| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- // Copyright 2010-2013 Wincent Colaiuta. All rights reserved.
- //
- // Redistribution and use in source and binary forms, with or without
- // modification, are permitted provided that the following conditions are met:
- //
- // 1. Redistributions of source code must retain the above copyright notice,
- // this list of conditions and the following disclaimer.
- // 2. Redistributions in binary form must reproduce the above copyright notice,
- // this list of conditions and the following disclaimer in the documentation
- // and/or other materials provided with the distribution.
- //
- // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
- // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- // POSSIBILITY OF SUCH DAMAGE.
- #include <float.h> /* for DBL_MAX */
- #include "match.h"
- #include "ext.h"
- #include "ruby_compat.h"
- // use a struct to make passing params during recursion easier
- typedef struct {
- char *haystack_p; // pointer to the path string to be searched
- long haystack_len; // length of same
- char *needle_p; // pointer to search string (needle)
- long needle_len; // length of same
- double max_score_per_char;
- int dot_file; // boolean: true if str is a dot-file
- int always_show_dot_files; // boolean
- int never_show_dot_files; // boolean
- double *memo; // memoization
- } matchinfo_t;
- double recursive_match(matchinfo_t *m, // sharable meta-data
- long haystack_idx, // where in the path string to start
- long needle_idx, // where in the needle string to start
- long last_idx, // location of last matched character
- double score) // cumulative score so far
- {
- double seen_score = 0; // remember best score seen via recursion
- int dot_file_match = 0; // true if needle matches a dot-file
- int dot_search = 0; // true if searching for a dot
- long i, j, distance;
- int found;
- double score_for_char;
- // do we have a memoized result we can return?
- double memoized = m->memo[needle_idx * m->needle_len + haystack_idx];
- if (memoized != DBL_MAX)
- return memoized;
- // bail early if not enough room (left) in haystack for (rest of) needle
- if (m->haystack_len - haystack_idx < m->needle_len - needle_idx) {
- score = 0.0;
- goto memoize;
- }
- for (i = needle_idx; i < m->needle_len; i++) {
- char c = m->needle_p[i];
- if (c == '.')
- dot_search = 1;
- found = 0;
- // similar to above, we'll stop iterating when we know we're too close
- // to the end of the string to possibly match
- for (j = haystack_idx;
- j <= m->haystack_len - (m->needle_len - i);
- j++, haystack_idx++) {
- char d = m->haystack_p[j];
- if (d == '.') {
- if (j == 0 || m->haystack_p[j - 1] == '/') {
- m->dot_file = 1; // this is a dot-file
- if (dot_search) // and we are searching for a dot
- dot_file_match = 1; // so this must be a match
- }
- } else if (d >= 'A' && d <= 'Z') {
- d += 'a' - 'A'; // add 32 to downcase
- }
- if (c == d) {
- found = 1;
- dot_search = 0;
- // calculate score
- score_for_char = m->max_score_per_char;
- distance = j - last_idx;
- if (distance > 1) {
- double factor = 1.0;
- char last = m->haystack_p[j - 1];
- char curr = m->haystack_p[j]; // case matters, so get again
- if (last == '/')
- factor = 0.9;
- else if (last == '-' ||
- last == '_' ||
- last == ' ' ||
- (last >= '0' && last <= '9'))
- factor = 0.8;
- else if (last >= 'a' && last <= 'z' &&
- curr >= 'A' && curr <= 'Z')
- factor = 0.8;
- else if (last == '.')
- factor = 0.7;
- else
- // if no "special" chars behind char, factor diminishes
- // as distance from last matched char increases
- factor = (1.0 / distance) * 0.75;
- score_for_char *= factor;
- }
- if (++j < m->haystack_len) {
- // bump cursor one char to the right and
- // use recursion to try and find a better match
- double sub_score = recursive_match(m, j, i, last_idx, score);
- if (sub_score > seen_score)
- seen_score = sub_score;
- }
- score += score_for_char;
- last_idx = haystack_idx + 1;
- break;
- }
- }
- if (!found) {
- score = 0.0;
- goto memoize;
- }
- }
- if (m->dot_file &&
- (m->never_show_dot_files ||
- (!dot_file_match && !m->always_show_dot_files))) {
- score = 0.0;
- goto memoize;
- }
- score = score > seen_score ? score : seen_score;
- memoize:
- m->memo[needle_idx * m->needle_len + haystack_idx] = score;
- return score;
- }
- void calculate_match(VALUE str,
- VALUE needle,
- VALUE always_show_dot_files,
- VALUE never_show_dot_files,
- match_t *out)
- {
- long i, max;
- double score;
- matchinfo_t m;
- m.haystack_p = RSTRING_PTR(str);
- m.haystack_len = RSTRING_LEN(str);
- m.needle_p = RSTRING_PTR(needle);
- m.needle_len = RSTRING_LEN(needle);
- m.max_score_per_char = (1.0 / m.haystack_len + 1.0 / m.needle_len) / 2;
- m.dot_file = 0;
- m.always_show_dot_files = always_show_dot_files == Qtrue;
- m.never_show_dot_files = never_show_dot_files == Qtrue;
- // calculate score
- score = 1.0;
- // special case for zero-length search string
- if (m.needle_len == 0) {
- // filter out dot files
- if (!m.always_show_dot_files) {
- for (i = 0; i < m.haystack_len; i++) {
- char c = m.haystack_p[i];
- if (c == '.' && (i == 0 || m.haystack_p[i - 1] == '/')) {
- score = 0.0;
- break;
- }
- }
- }
- } else if (m.haystack_len > 0) { // normal case
- // prepare for memoization
- double memo[m.haystack_len * m.needle_len];
- for (i = 0, max = m.haystack_len * m.needle_len; i < max; i++)
- memo[i] = DBL_MAX;
- m.memo = memo;
- score = recursive_match(&m, 0, 0, 0, 0.0);
- }
- // final book-keeping
- out->path = str;
- out->score = score;
- }
|