4 * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav
5 * Copyright (C) 2008-2011 Gabor Kovesdan <gabor@FreeBSD.org>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 #include "hashtable.h"
44 #include "tre-fastmatch.h"
47 static int fastcmp(const fastmatch_t *fg, const void *data,
51 * Clean up if pattern compilation fails.
53 #define FAIL_COMP(errcode) \
58 xfree(fg->wpattern); \
60 hashtable_free(fg->qsBc_table); \
66 * Skips n characters in the input string and assigns the start
67 * address to startptr. Note: as per IEEE Std 1003.1-2008
68 * matching is based on bit pattern not character representations
69 * so we can handle MB strings as byte sequences just like
72 #define SKIP_CHARS(n) \
76 startptr = str_wide + n; \
79 startptr = str_byte + n; \
83 * Converts the wide string pattern to SB/MB string and stores
84 * it in fg->pattern. Sets fg->len to the byte length of the
87 #define STORE_MBS_PAT \
91 siz = wcstombs(NULL, fg->wpattern, 0); \
92 if (siz == (size_t)-1) \
95 fg->pattern = xmalloc(siz + 1); \
96 if (fg->pattern == NULL) \
98 wcstombs(fg->pattern, fg->wpattern, siz); \
99 fg->pattern[siz] = '\0'; \
102 #define IS_OUT_OF_BOUNDS \
104 ? ((type == STR_WIDE) ? ((j + fg->wlen) > len) \
105 : ((j + fg->len) > len)) \
109 * Checks whether the new position after shifting in the input string
110 * is out of the bounds and break out from the loop if so.
112 #define CHECKBOUNDS \
113 if (IS_OUT_OF_BOUNDS) \
117 * Shifts in the input string after a mismatch. The position of the
118 * mismatch is stored in the mismatch variable.
125 unsigned int bc = 0, gs = 0, ts; \
132 if (u != 0 && (unsigned)mismatch == fg->wlen - 1 - shift) \
134 v = fg->wlen - 1 - mismatch; \
135 r = hashtable_get(fg->qsBc_table, \
136 &str_wide[!fg->reversed ? (size_t)j + fg->wlen \
137 : (size_t)j - 1], &bc); \
138 gs = fg->bmGs[mismatch]; \
140 bc = (r == HASH_OK) ? bc : fg->defBc; \
141 DPRINT(("tre_fast_match: mismatch on character" CHF ", " \
143 ((const tre_char_t *)startptr)[mismatch + 1], \
149 if (u != 0 && (unsigned)mismatch == fg->len - 1 - shift) \
151 v = fg->len - 1 - mismatch; \
152 gs = fg->sbmGs[mismatch]; \
154 bc = fg->qsBc[((const unsigned char *)str_byte) \
155 [!fg->reversed ? (size_t)j + fg->len \
157 DPRINT(("tre_fast_match: mismatch on character %c, " \
159 ((const unsigned char *)startptr)[mismatch + 1], \
166 ts = (u >= v) ? (u - v) : 0; \
167 shift = MAX(ts, bc); \
168 shift = MAX(shift, gs); \
170 u = MIN((type == STR_WIDE ? fg->wlen : fg->len) - shift, v); \
174 shift = MAX(shift, u + 1); \
178 DPRINT(("tre_fast_match: shifting %u characters\n", shift)); \
179 j = !fg->reversed ? j + shift : j - shift; \
183 * Normal Quick Search would require a shift based on the position the
184 * next character after the comparison is within the pattern. With
185 * wildcards, the position of the last dot effects the maximum shift
187 * The closer to the end the wild card is the slower the search.
200 * Fills in the bad character shift array for SB/MB strings.
205 _FILL_QSBC_REVERSED \
213 for (unsigned int i = 0; i <= UCHAR_MAX; i++) \
214 fg->qsBc[i] = fg->len - hasdot; \
215 for (unsigned int i = hasdot + 1; i < fg->len; i++) \
217 fg->qsBc[(unsigned char)fg->pattern[i]] = fg->len - i; \
218 DPRINT(("BC shift for char %c is %zu\n", fg->pattern[i], \
222 char c = islower((unsigned char)fg->pattern[i]) ? \
223 toupper((unsigned char)fg->pattern[i]) : \
224 tolower((unsigned char)fg->pattern[i]); \
225 fg->qsBc[(unsigned char)c] = fg->len - i; \
226 DPRINT(("BC shift for char %c is %zu\n", c, fg->len - i)); \
230 #define _FILL_QSBC_REVERSED \
231 for (unsigned int i = 0; i <= UCHAR_MAX; i++) \
232 fg->qsBc[i] = firstdot + 1; \
233 for (int i = firstdot - 1; i >= 0; i--) \
235 fg->qsBc[(unsigned char)fg->pattern[i]] = i + 1; \
236 DPRINT(("Reverse BC shift for char %c is %d\n", fg->pattern[i], \
240 char c = islower((unsigned char)fg->pattern[i]) ? \
241 toupper((unsigned char)fg->pattern[i]) : \
242 tolower((unsigned char)fg->pattern[i]); \
243 fg->qsBc[(unsigned char)c] = i + 1; \
244 DPRINT(("Reverse BC shift for char %c is %d\n", c, i + 1)); \
249 * Fills in the bad character shifts into a hastable for wide strings.
250 * With wide characters it is not possible any more to use a normal
251 * array because there are too many characters and we could not
252 * provide enough memory. Fortunately, we only have to store distinct
253 * values for so many characters as the number of distinct characters
254 * in the pattern, so we can store them in a hashtable and store a
255 * default shift value for the rest.
257 #define FILL_QSBC_WIDE \
260 _FILL_QSBC_WIDE_REVERSED \
267 #define _FILL_QSBC_WIDE \
268 /* Adjust the shift based on location of the last dot ('.'). */ \
269 fg->defBc = fg->wlen - whasdot; \
271 /* Preprocess pattern. */ \
272 fg->qsBc_table = hashtable_init(fg->wlen * (fg->icase ? 8 : 4), \
273 sizeof(tre_char_t), sizeof(int)); \
274 if (!fg->qsBc_table) \
275 FAIL_COMP(REG_ESPACE); \
276 for (unsigned int i = whasdot + 1; i < fg->wlen; i++) \
278 int k = fg->wlen - i; \
281 r = hashtable_put(fg->qsBc_table, &fg->wpattern[i], &k); \
282 if ((r == HASH_FAIL) || (r == HASH_FULL)) \
283 FAIL_COMP(REG_ESPACE); \
284 DPRINT(("BC shift for wide char " CHF " is %d\n", fg->wpattern[i],\
288 tre_char_t wc = iswlower(fg->wpattern[i]) ? \
289 towupper(fg->wpattern[i]) : towlower(fg->wpattern[i]); \
290 r = hashtable_put(fg->qsBc_table, &wc, &k); \
291 if ((r == HASH_FAIL) || (r == HASH_FULL)) \
292 FAIL_COMP(REG_ESPACE); \
293 DPRINT(("BC shift for wide char " CHF " is %d\n", wc, k)); \
297 #define _FILL_QSBC_WIDE_REVERSED \
298 /* Adjust the shift based on location of the last dot ('.'). */ \
299 fg->defBc = (size_t)wfirstdot; \
301 /* Preprocess pattern. */ \
302 fg->qsBc_table = hashtable_init(fg->wlen * (fg->icase ? 8 : 4), \
303 sizeof(tre_char_t), sizeof(int)); \
304 if (!fg->qsBc_table) \
305 FAIL_COMP(REG_ESPACE); \
306 for (int i = wfirstdot - 1; i >= 0; i--) \
311 r = hashtable_put(fg->qsBc_table, &fg->wpattern[i], &k); \
312 if ((r == HASH_FAIL) || (r == HASH_FULL)) \
313 FAIL_COMP(REG_ESPACE); \
314 DPRINT(("Reverse BC shift for wide char " CHF " is %d\n", \
315 fg->wpattern[i], k)); \
318 tre_char_t wc = iswlower(fg->wpattern[i]) ? \
319 towupper(fg->wpattern[i]) : towlower(fg->wpattern[i]); \
320 r = hashtable_put(fg->qsBc_table, &wc, &k); \
321 if ((r == HASH_FAIL) || (r == HASH_FULL)) \
322 FAIL_COMP(REG_ESPACE); \
323 DPRINT(("Reverse BC shift for wide char " CHF " is %d\n", wc, \
329 #define DPRINT_BMGS(len, fmt_str, sh) \
330 for (unsigned int i = 0; i < len; i++) \
331 DPRINT((fmt_str, i, sh[i]));
333 #define DPRINT_BMGS(len, fmt_str, sh) \
334 do { } while(/*CONSTCOND*/0)
338 * Fills in the good suffix table for SB/MB strings.
343 fg->sbmGs = xmalloc(fg->len * sizeof(int)); \
349 _FILL_BMGS(fg->sbmGs, fg->pattern, fg->len, false); \
350 DPRINT_BMGS(fg->len, "GS shift for pos %d is %d\n", fg->sbmGs); \
354 * Fills in the good suffix table for wide strings.
356 #define FILL_BMGS_WIDE \
359 fg->bmGs = xmalloc(fg->wlen * sizeof(int)); \
365 _FILL_BMGS(fg->bmGs, fg->wpattern, fg->wlen, true); \
366 DPRINT_BMGS(fg->wlen, "GS shift (wide) for pos %d is %d\n", \
370 #define _FILL_BMGS(arr, pat, plen, wide) \
379 wp = xmalloc(plen * sizeof(tre_char_t)); \
382 for (unsigned int i = 0; i < plen; i++) \
383 wp[i] = towlower(pat[i]); \
384 _CALC_BMGS(arr, wp, plen); \
388 _CALC_BMGS(arr, pat, plen); \
397 for (unsigned int i = 0; i < plen; i++) \
398 p[i] = tolower((unsigned char)pat[i]); \
399 _CALC_BMGS(arr, p, plen); \
403 _CALC_BMGS(arr, pat, plen); \
407 #define _CALC_BMGS(arr, pat, plen) \
411 int *suff = xmalloc(plen * sizeof(int)); \
415 suff[plen - 1] = plen; \
417 for (int i = plen - 2; i >= 0; i--) \
419 if (i > g && suff[i + plen - 1 - f] < i - g) \
420 suff[i] = suff[i + plen - 1 - f]; \
426 while (g >= 0 && pat[g] == pat[g + plen - 1 - f]) \
432 for (unsigned int i = 0; i < plen; i++) \
435 for (int i = plen - 1; i >= 0; i--) \
436 if (suff[i] == i + 1) \
437 for(; (unsigned long)g < plen - 1 - i; g++) \
438 if (arr[g] == plen) \
439 arr[g] = plen - 1 - i; \
440 for (unsigned int i = 0; i <= plen - 2; i++) \
441 arr[plen - 1 - suff[i]] = plen - 1 - i; \
447 * Copies the pattern pat having length n to p and stores
450 #define SAVE_PATTERN(src, srclen, dst, dstlen) \
452 dst = xmalloc((dstlen + 1) * sizeof(tre_char_t)); \
456 memcpy(dst, src, dstlen * sizeof(tre_char_t)); \
457 dst[dstlen] = TRE_CHAR('\0');
460 * Initializes pattern compiling.
464 memset(fg, 0, sizeof(*fg)); \
465 fg->icase = (cflags & REG_ICASE); \
466 fg->word = (cflags & REG_WORD); \
467 fg->newline = (cflags & REG_NEWLINE); \
468 fg->nosub = (cflags & REG_NOSUB); \
470 /* Cannot handle REG_ICASE with MB string */ \
471 if (fg->icase && (TRE_MB_CUR_MAX > 1) && n > 0) \
473 DPRINT(("Cannot use fast matcher for MBS with REG_ICASE\n")); \
478 * Checks whether we have a 0-length pattern that will match
479 * anything. If literal is set to false, the EOL anchor is also
480 * taken into account.
482 #define CHECK_MATCHALL(literal) \
483 if (!literal && n == 1 && pat[0] == TRE_CHAR('$')) \
491 fg->matchall = true; \
492 fg->pattern = xmalloc(sizeof(char)); \
494 FAIL_COMP(REG_ESPACE); \
495 fg->pattern[0] = '\0'; \
496 fg->wpattern = xmalloc(sizeof(tre_char_t)); \
498 FAIL_COMP(REG_ESPACE); \
499 fg->wpattern[0] = TRE_CHAR('\0'); \
500 DPRINT(("Matching every input\n")); \
505 * Returns: REG_OK on success, error code otherwise
508 tre_compile_literal(fastmatch_t *fg, const tre_char_t *pat, size_t n,
511 size_t hasdot = 0, whasdot = 0;
512 ssize_t firstdot = -1, wfirstdot = -1;
516 CHECK_MATCHALL(true);
518 /* Cannot handle word boundaries with MB string */
519 if (fg->word && (TRE_MB_CUR_MAX > 1))
523 SAVE_PATTERN(pat, n, fg->wpattern, fg->wlen);
526 SAVE_PATTERN(pat, n, fg->pattern, fg->len);
529 DPRINT(("tre_compile_literal: pattern: %s, len %zu, icase: %c, word: %c, "
530 "newline %c\n", fg->pattern, fg->len, fg->icase ? 'y' : 'n',
531 fg->word ? 'y' : 'n', fg->newline ? 'y' : 'n'));
544 * Returns: REG_OK on success, error code otherwise
547 tre_compile_fast(fastmatch_t *fg, const tre_char_t *pat, size_t n,
551 size_t pos = 0, hasdot = 0, whasdot = 0;
552 ssize_t firstdot = -1, wfirstdot = -1;
553 bool escaped = false;
554 bool *_escmap = NULL;
558 /* Remove beginning-of-line character ('^'). */
559 if (pat[0] == TRE_CHAR('^'))
566 CHECK_MATCHALL(false);
568 /* Handle word-boundary matching when GNU extensions are enabled */
569 if ((cflags & REG_GNU) && (n >= 14) &&
570 (memcmp(pat, TRE_CHAR("[[:<:]]"), 7 * sizeof(tre_char_t)) == 0) &&
571 (memcmp(pat + n - 7, TRE_CHAR("[[:>:]]"),
572 7 * sizeof(tre_char_t)) == 0))
579 /* Cannot handle word boundaries with MB string */
580 if (fg->word && (TRE_MB_CUR_MAX > 1))
583 tmp = xmalloc((n + 1) * sizeof(tre_char_t));
587 /* Copies the char into the stored pattern and skips to the next char. */
591 tmp[pos++] = pat[i]; \
596 /* Traverse the input pattern for processing */
597 for (unsigned int i = 0; i < n; i++)
616 if (escaped || (!(cflags & REG_EXTENDED) && (i == 0)))
623 if ((cflags & REG_EXTENDED) && (i == 0))
625 else if ((cflags & REG_EXTENDED) ^ !escaped)
634 _escmap = xmalloc(n * sizeof(bool));
655 if (!escaped && (i == n - 1))
661 if ((cflags & REG_EXTENDED) ^ escaped)
667 if (!(cflags & REG_EXTENDED) ^ escaped)
669 else if (!(cflags & REG_EXTENDED) && (i == 0))
671 else if ((cflags & REG_EXTENDED) && (i == 0))
677 if ((cflags & REG_EXTENDED) ^ escaped)
692 DPRINT(("tre_compile_fast: compilation of pattern failed, falling"
697 fg->hasdot = wfirstdot > -1;
700 * The pattern has been processed and copied to tmp as a literal string
701 * with escapes, anchors (^$) and the word boundary match character
702 * classes stripped out.
705 SAVE_PATTERN(tmp, pos, fg->wpattern, fg->wlen);
706 fg->wescmap = _escmap;
710 * The position of dots and escaped dots is different in the MB string
711 * than in to the wide string so traverse the converted string, as well,
712 * to store these positions.
714 if (fg->hasdot || (fg->wescmap != NULL))
716 if (fg->wescmap != NULL)
718 fg->escmap = xmalloc(fg->len * sizeof(bool));
727 for (unsigned int i = 0; i < fg->len; i++)
728 if (fg->pattern[i] == '\\')
730 else if (fg->pattern[i] == '.' && fg->escmap && escaped)
732 fg->escmap[i] = true;
735 else if (fg->pattern[i] == '.' && !escaped)
745 SAVE_PATTERN(tmp, pos, fg->pattern, fg->len);
746 fg->escmap = _escmap;
751 DPRINT(("tre_compile_fast: pattern: %s, len %zu, bol %c, eol %c, "
752 "icase: %c, word: %c, newline %c\n", fg->pattern, fg->len,
753 fg->bol ? 'y' : 'n', fg->eol ? 'y' : 'n',
754 fg->icase ? 'y' : 'n', fg->word ? 'y' : 'n',
755 fg->newline ? 'y' : 'n'));
757 /* Check whether reverse QS algorithm is more efficient */
758 if ((wfirstdot > -1) && (fg->wlen - whasdot + 1 < (size_t)wfirstdot) &&
762 DPRINT(("tre_compile_fast: using reverse QS algorithm\n"));
778 j = !fg->reversed ? j + shift : j - shift; \
782 #define _BBOUND_COND \
783 ((type == STR_WIDE) ? \
784 ((j == 0) || !(tre_isalnum(str_wide[j - 1]) || \
785 (str_wide[j - 1] == TRE_CHAR('_')))) : \
786 ((j == 0) || !(tre_isalnum(str_byte[j - 1]) || \
787 (str_byte[j - 1] == '_'))))
789 #define _EBOUND_COND \
790 ((type == STR_WIDE) ? \
791 ((j + fg->wlen == len) || !(tre_isalnum(str_wide[j + fg->wlen]) || \
792 (str_wide[j + fg->wlen] == TRE_CHAR('_')))) : \
793 ((j + fg->len == len) || !(tre_isalnum(str_byte[j + fg->len]) || \
794 (str_byte[j + fg->len] == '_'))))
797 * Condition to check whether the match on position j is on a
800 #define IS_ON_WORD_BOUNDARY \
801 (_BBOUND_COND && _EBOUND_COND)
804 * Checks word boundary and shifts one if match is not on a
807 #define CHECK_WORD_BOUNDARY \
808 if (!IS_ON_WORD_BOUNDARY) \
812 ((j == 0) || ((type == STR_WIDE) ? (str_wide[j - 1] == TRE_CHAR('\n'))\
813 : (str_byte[j - 1] == '\n')))
816 * Checks BOL anchor and shifts one if match is not on a
819 #define CHECK_BOL_ANCHOR \
824 ((type == STR_WIDE) \
825 ? ((j + fg->wlen == len) || \
826 (str_wide[j + fg->wlen] == TRE_CHAR('\n'))) \
827 : ((j + fg->len == len) || (str_byte[j + fg->wlen] == '\n')))
830 * Checks EOL anchor and shifts one if match is not on a
833 #define CHECK_EOL_ANCHOR \
838 * Executes matching of the precompiled pattern on the input string.
839 * Returns REG_OK or REG_NOMATCH depending on if we find a match or not.
842 tre_match_fast(const fastmatch_t *fg, const void *data, size_t len,
843 tre_str_type_t type, int nmatch, regmatch_t pmatch[], int eflags)
845 unsigned int shift, u = 0, v = 0;
847 int ret = REG_NOMATCH;
849 const char *str_byte = data;
850 const void *startptr = NULL;
851 const tre_char_t *str_wide = data;
853 /* Calculate length if unspecified. */
854 if (len == (size_t)-1)
858 len = tre_strlen(str_wide);
861 len = strlen(str_byte);
865 /* Shortcut for empty pattern */
868 if (!fg->nosub && nmatch >= 1)
871 pmatch[0].rm_eo = len;
873 if (fg->bol && fg->eol)
874 return (len == 0) ? REG_OK : REG_NOMATCH;
879 /* No point in going farther if we do not have enough data. */
894 * REG_NOTBOL means not anchoring ^ to the beginning of the line, so we
895 * can shift one because there can't be a match at the beginning.
897 if (fg->bol && (eflags & REG_NOTBOL))
901 * Like above, we cannot have a match at the very end when anchoring to
902 * the end and REG_NOTEOL is specified.
904 if (fg->eol && (eflags & REG_NOTEOL))
908 j = len - (type == STR_WIDE ? fg->wlen : fg->len);
911 /* Only try once at the beginning or ending of the line. */
912 if ((fg->bol || fg->eol) && !fg->newline && !(eflags & REG_NOTBOL) &&
913 !(eflags & REG_NOTEOL))
915 /* Simple text comparison. */
916 if (!((fg->bol && fg->eol) &&
917 (type == STR_WIDE ? (len != fg->wlen) : (len != fg->len))))
919 /* Determine where in data to start search at. */
920 j = fg->eol ? len - (type == STR_WIDE ? fg->wlen : fg->len) : 0;
922 mismatch = fastcmp(fg, startptr, type);
923 if (mismatch == REG_OK)
925 if (fg->word && !IS_ON_WORD_BOUNDARY)
927 if (!fg->nosub && nmatch >= 1)
930 pmatch[0].rm_eo = j + (type == STR_WIDE ? fg->wlen : fg->len);
938 /* Quick Search / Turbo Boyer-Moore algorithm. */
942 mismatch = fastcmp(fg, startptr, type);
943 if (mismatch == REG_OK)
951 if (!fg->nosub && nmatch >= 1)
954 pmatch[0].rm_eo = j + ((type == STR_WIDE) ? fg->wlen : fg->len);
958 else if (mismatch > 0)
960 mismatch = -mismatch - 1;
962 } while (!IS_OUT_OF_BOUNDS);
968 * Frees the resources that were allocated when the pattern was compiled.
971 tre_free_fast(fastmatch_t *fg)
974 DPRINT(("tre_fast_free: freeing structures for pattern %s\n",
978 hashtable_free(fg->qsBc_table);
993 * Returns: -(i + 1) on failure (position that it failed with minus sign)
994 * error code on error
998 fastcmp(const fastmatch_t *fg, const void *data, tre_str_type_t type)
1000 const char *str_byte = data;
1001 const char *pat_byte = fg->pattern;
1002 const tre_char_t *str_wide = data;
1003 const tre_char_t *pat_wide = fg->wpattern;
1004 const bool *escmap = (type == STR_WIDE) ? fg->wescmap : fg->escmap;
1005 size_t len = (type == STR_WIDE) ? fg->wlen : fg->len;
1008 /* Compare the pattern and the input char-by-char from the last position. */
1009 for (int i = len - 1; i >= 0; i--) {
1015 if (fg->hasdot && pat_wide[i] == TRE_CHAR('.') &&
1016 (!escmap || !escmap[i]) &&
1017 (!fg->newline || (str_wide[i] != TRE_CHAR('\n'))))
1021 if (fg->icase ? (towlower(pat_wide[i]) == towlower(str_wide[i]))
1022 : (pat_wide[i] == str_wide[i]))
1027 if (fg->hasdot && pat_byte[i] == '.' &&
1028 (!escmap || !escmap[i]) &&
1029 (!fg->newline || (str_byte[i] != '\n')))
1033 if (fg->icase ? (tolower((unsigned char)pat_byte[i]) == tolower((unsigned char)str_byte[i]))
1034 : (pat_byte[i] == str_byte[i]))
1037 DPRINT(("fastcmp: mismatch at position %d\n", i));