Home | History | Annotate | Download | only in stringlib
      1 /* stringlib: fastsearch implementation */
      2 
      3 #define STRINGLIB_FASTSEARCH_H
      4 
      5 /* fast search/count implementation, based on a mix between boyer-
      6    moore and horspool, with a few more bells and whistles on the top.
      7    for some more background, see: http://effbot.org/zone/stringlib.htm */
      8 
      9 /* note: fastsearch may access s[n], which isn't a problem when using
     10    Python's ordinary string types, but may cause problems if you're
     11    using this code in other contexts.  also, the count mode returns -1
     12    if there cannot possible be a match in the target string, and 0 if
     13    it has actually checked for matches, but didn't find any.  callers
     14    beware! */
     15 
     16 #define FAST_COUNT 0
     17 #define FAST_SEARCH 1
     18 #define FAST_RSEARCH 2
     19 
     20 #if LONG_BIT >= 128
     21 #define STRINGLIB_BLOOM_WIDTH 128
     22 #elif LONG_BIT >= 64
     23 #define STRINGLIB_BLOOM_WIDTH 64
     24 #elif LONG_BIT >= 32
     25 #define STRINGLIB_BLOOM_WIDTH 32
     26 #else
     27 #error "LONG_BIT is smaller than 32"
     28 #endif
     29 
     30 #define STRINGLIB_BLOOM_ADD(mask, ch) \
     31     ((mask |= (1UL << ((ch) & (STRINGLIB_BLOOM_WIDTH -1)))))
     32 #define STRINGLIB_BLOOM(mask, ch)     \
     33     ((mask &  (1UL << ((ch) & (STRINGLIB_BLOOM_WIDTH -1)))))
     34 
     35 #if STRINGLIB_SIZEOF_CHAR == 1
     36 #  define MEMCHR_CUT_OFF 15
     37 #else
     38 #  define MEMCHR_CUT_OFF 40
     39 #endif
     40 
     41 Py_LOCAL_INLINE(Py_ssize_t)
     42 STRINGLIB(find_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch)
     43 {
     44     const STRINGLIB_CHAR *p, *e;
     45 
     46     p = s;
     47     e = s + n;
     48     if (n > MEMCHR_CUT_OFF) {
     49 #if STRINGLIB_SIZEOF_CHAR == 1
     50         p = memchr(s, ch, n);
     51         if (p != NULL)
     52             return (p - s);
     53         return -1;
     54 #else
     55         /* use memchr if we can choose a needle without two many likely
     56            false positives */
     57         const STRINGLIB_CHAR *s1, *e1;
     58         unsigned char needle = ch & 0xff;
     59         /* If looking for a multiple of 256, we'd have too
     60            many false positives looking for the '\0' byte in UCS2
     61            and UCS4 representations. */
     62         if (needle != 0) {
     63             do {
     64                 void *candidate = memchr(p, needle,
     65                                          (e - p) * sizeof(STRINGLIB_CHAR));
     66                 if (candidate == NULL)
     67                     return -1;
     68                 s1 = p;
     69                 p = (const STRINGLIB_CHAR *)
     70                         _Py_ALIGN_DOWN(candidate, sizeof(STRINGLIB_CHAR));
     71                 if (*p == ch)
     72                     return (p - s);
     73                 /* False positive */
     74                 p++;
     75                 if (p - s1 > MEMCHR_CUT_OFF)
     76                     continue;
     77                 if (e - p <= MEMCHR_CUT_OFF)
     78                     break;
     79                 e1 = p + MEMCHR_CUT_OFF;
     80                 while (p != e1) {
     81                     if (*p == ch)
     82                         return (p - s);
     83                     p++;
     84                 }
     85             }
     86             while (e - p > MEMCHR_CUT_OFF);
     87         }
     88 #endif
     89     }
     90     while (p < e) {
     91         if (*p == ch)
     92             return (p - s);
     93         p++;
     94     }
     95     return -1;
     96 }
     97 
     98 Py_LOCAL_INLINE(Py_ssize_t)
     99 STRINGLIB(rfind_char)(const STRINGLIB_CHAR* s, Py_ssize_t n, STRINGLIB_CHAR ch)
    100 {
    101     const STRINGLIB_CHAR *p;
    102 #ifdef HAVE_MEMRCHR
    103     /* memrchr() is a GNU extension, available since glibc 2.1.91.
    104        it doesn't seem as optimized as memchr(), but is still quite
    105        faster than our hand-written loop below */
    106 
    107     if (n > MEMCHR_CUT_OFF) {
    108 #if STRINGLIB_SIZEOF_CHAR == 1
    109         p = memrchr(s, ch, n);
    110         if (p != NULL)
    111             return (p - s);
    112         return -1;
    113 #else
    114         /* use memrchr if we can choose a needle without two many likely
    115            false positives */
    116         const STRINGLIB_CHAR *s1;
    117         Py_ssize_t n1;
    118         unsigned char needle = ch & 0xff;
    119         /* If looking for a multiple of 256, we'd have too
    120            many false positives looking for the '\0' byte in UCS2
    121            and UCS4 representations. */
    122         if (needle != 0) {
    123             do {
    124                 void *candidate = memrchr(s, needle,
    125                                           n * sizeof(STRINGLIB_CHAR));
    126                 if (candidate == NULL)
    127                     return -1;
    128                 n1 = n;
    129                 p = (const STRINGLIB_CHAR *)
    130                         _Py_ALIGN_DOWN(candidate, sizeof(STRINGLIB_CHAR));
    131                 n = p - s;
    132                 if (*p == ch)
    133                     return n;
    134                 /* False positive */
    135                 if (n1 - n > MEMCHR_CUT_OFF)
    136                     continue;
    137                 if (n <= MEMCHR_CUT_OFF)
    138                     break;
    139                 s1 = p - MEMCHR_CUT_OFF;
    140                 while (p > s1) {
    141                     p--;
    142                     if (*p == ch)
    143                         return (p - s);
    144                 }
    145                 n = p - s;
    146             }
    147             while (n > MEMCHR_CUT_OFF);
    148         }
    149 #endif
    150     }
    151 #endif  /* HAVE_MEMRCHR */
    152     p = s + n;
    153     while (p > s) {
    154         p--;
    155         if (*p == ch)
    156             return (p - s);
    157     }
    158     return -1;
    159 }
    160 
    161 #undef MEMCHR_CUT_OFF
    162 
    163 Py_LOCAL_INLINE(Py_ssize_t)
    164 FASTSEARCH(const STRINGLIB_CHAR* s, Py_ssize_t n,
    165            const STRINGLIB_CHAR* p, Py_ssize_t m,
    166            Py_ssize_t maxcount, int mode)
    167 {
    168     unsigned long mask;
    169     Py_ssize_t skip, count = 0;
    170     Py_ssize_t i, j, mlast, w;
    171 
    172     w = n - m;
    173 
    174     if (w < 0 || (mode == FAST_COUNT && maxcount == 0))
    175         return -1;
    176 
    177     /* look for special cases */
    178     if (m <= 1) {
    179         if (m <= 0)
    180             return -1;
    181         /* use special case for 1-character strings */
    182         if (mode == FAST_SEARCH)
    183             return STRINGLIB(find_char)(s, n, p[0]);
    184         else if (mode == FAST_RSEARCH)
    185             return STRINGLIB(rfind_char)(s, n, p[0]);
    186         else {  /* FAST_COUNT */
    187             for (i = 0; i < n; i++)
    188                 if (s[i] == p[0]) {
    189                     count++;
    190                     if (count == maxcount)
    191                         return maxcount;
    192                 }
    193             return count;
    194         }
    195         return -1;
    196     }
    197 
    198     mlast = m - 1;
    199     skip = mlast - 1;
    200     mask = 0;
    201 
    202     if (mode != FAST_RSEARCH) {
    203         const STRINGLIB_CHAR *ss = s + m - 1;
    204         const STRINGLIB_CHAR *pp = p + m - 1;
    205 
    206         /* create compressed boyer-moore delta 1 table */
    207 
    208         /* process pattern[:-1] */
    209         for (i = 0; i < mlast; i++) {
    210             STRINGLIB_BLOOM_ADD(mask, p[i]);
    211             if (p[i] == p[mlast])
    212                 skip = mlast - i - 1;
    213         }
    214         /* process pattern[-1] outside the loop */
    215         STRINGLIB_BLOOM_ADD(mask, p[mlast]);
    216 
    217         for (i = 0; i <= w; i++) {
    218             /* note: using mlast in the skip path slows things down on x86 */
    219             if (ss[i] == pp[0]) {
    220                 /* candidate match */
    221                 for (j = 0; j < mlast; j++)
    222                     if (s[i+j] != p[j])
    223                         break;
    224                 if (j == mlast) {
    225                     /* got a match! */
    226                     if (mode != FAST_COUNT)
    227                         return i;
    228                     count++;
    229                     if (count == maxcount)
    230                         return maxcount;
    231                     i = i + mlast;
    232                     continue;
    233                 }
    234                 /* miss: check if next character is part of pattern */
    235                 if (!STRINGLIB_BLOOM(mask, ss[i+1]))
    236                     i = i + m;
    237                 else
    238                     i = i + skip;
    239             } else {
    240                 /* skip: check if next character is part of pattern */
    241                 if (!STRINGLIB_BLOOM(mask, ss[i+1]))
    242                     i = i + m;
    243             }
    244         }
    245     } else {    /* FAST_RSEARCH */
    246 
    247         /* create compressed boyer-moore delta 1 table */
    248 
    249         /* process pattern[0] outside the loop */
    250         STRINGLIB_BLOOM_ADD(mask, p[0]);
    251         /* process pattern[:0:-1] */
    252         for (i = mlast; i > 0; i--) {
    253             STRINGLIB_BLOOM_ADD(mask, p[i]);
    254             if (p[i] == p[0])
    255                 skip = i - 1;
    256         }
    257 
    258         for (i = w; i >= 0; i--) {
    259             if (s[i] == p[0]) {
    260                 /* candidate match */
    261                 for (j = mlast; j > 0; j--)
    262                     if (s[i+j] != p[j])
    263                         break;
    264                 if (j == 0)
    265                     /* got a match! */
    266                     return i;
    267                 /* miss: check if previous character is part of pattern */
    268                 if (i > 0 && !STRINGLIB_BLOOM(mask, s[i-1]))
    269                     i = i - m;
    270                 else
    271                     i = i - skip;
    272             } else {
    273                 /* skip: check if previous character is part of pattern */
    274                 if (i > 0 && !STRINGLIB_BLOOM(mask, s[i-1]))
    275                     i = i - m;
    276             }
    277         }
    278     }
    279 
    280     if (mode != FAST_COUNT)
    281         return -1;
    282     return count;
    283 }
    284 
    285