source: tags/ms_ra2q2/ARBDB/admatch.cxx

Last change on this file was 17396, checked in by westram, 6 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 27.5 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : admatch.cxx                                       //
4//   Purpose   : functions related to string match/replace         //
5//                                                                 //
6//   ReCoded for POSIX ERE                                         //
7//            by Ralf Westram (coder@reallysoft.de) in April 2009  //
8//   Institute of Microbiology (Technical University Munich)       //
9//   http://www.arb-home.de/                                       //
10//                                                                 //
11// =============================================================== //
12
13#include "gb_local.h"
14
15#include "gb_aci_impl.h"
16
17#include <arb_strbuf.h>
18#include <arb_match.h>
19
20#include <cctype>
21
22using namespace GBL_IMPL;
23
24// ------------------------
25//      string matcher
26
27enum string_matcher_type {
28    SM_INVALID = -1,
29    SM_ANY     = 0,             // matches any string
30    SM_WILDCARDED,              // match with wildcards (GBS_string_matches)
31    SM_REGEXPR,                 // match using regexpr
32};
33
34struct GBS_string_matcher {
35    string_matcher_type  type;
36    GB_CASE              case_flag;
37    char                *wildexpr;
38    GBS_regex           *regexpr;
39};
40
41GBS_string_matcher *GBS_compile_matcher(const char *search_expr, GB_CASE case_flag) {
42    /* returns a valid string matcher (to be used with GBS_string_matches_regexp)
43     * or NULp (in which case an error was exported)
44     */
45
46    GBS_string_matcher *matcher = ARB_alloc<GBS_string_matcher>(1);
47    GB_ERROR            error   = NULp;
48
49    matcher->type      = SM_INVALID;
50    matcher->case_flag = case_flag;
51    matcher->wildexpr  = NULp;
52    matcher->regexpr   = NULp;
53
54    if (search_expr[0] == '/') {
55        const char *end = strchr(search_expr, 0)-1;
56        if (end>search_expr && end[0] == '/') {
57            GB_CASE     expr_attached_case;
58            const char *unwrapped_expr = GBS_unwrap_regexpr(search_expr, &expr_attached_case, &error);
59
60            if (unwrapped_expr) {
61                if (expr_attached_case != GB_MIND_CASE) error = "format '/../i' not allowed here";
62                else {
63                    matcher->regexpr = GBS_compile_regexpr(unwrapped_expr, case_flag, &error);
64                    if (matcher->regexpr) {
65                        matcher->type = SM_REGEXPR;
66                    }
67                }
68            }
69        }
70    }
71
72    if (!matcher->regexpr && !error) {
73        if (strcmp(search_expr, "*") == 0) {
74            matcher->type = SM_ANY;
75        }
76        else {
77            matcher->type     = SM_WILDCARDED;
78            matcher->wildexpr = ARB_strdup(search_expr);
79        }
80    }
81
82    if (matcher->type == SM_INVALID) {
83        error = GBS_global_string("Failed to create GBS_string_matcher from '%s'", search_expr);
84    }
85
86    if (error) {
87        GBS_free_matcher(matcher);
88        matcher = NULp;
89        GB_export_error(error);
90    }
91    return matcher;
92}
93
94void GBS_free_matcher(GBS_string_matcher *matcher) {
95    free(matcher->wildexpr);
96    if (matcher->regexpr) GBS_free_regexpr(matcher->regexpr);
97    free(matcher);
98}
99
100// -------------------------
101//      wildcard search
102
103GB_CSTR GBS_find_string(GB_CSTR cont, GB_CSTR substr, int match_mode) {
104    /* search a substring in another string
105     * match_mode == 0     -> exact match
106     * match_mode == 1     -> a==A
107     * match_mode == 2     -> a==a && a==?
108     * match_mode == else  -> a==A && a==?
109     */
110    const char *p1, *p2;
111    char        b;
112
113    switch (match_mode) {
114
115        case 0: // exact match
116            for (p1 = cont, p2 = substr; *p1;) {
117                if (!(b = *p2)) {
118                    return (char *)cont;
119                }
120                else {
121                    if (b == *p1) {
122                        p1++;
123                        p2++;
124                    }
125                    else {
126                        p2 = substr;
127                        p1 = (++cont);
128                    }
129                }
130            }
131            if (!*p2)   return (char *)cont;
132            break;
133
134        case 1: // a==A
135            for (p1 = cont, p2 = substr; *p1;) {
136                if (!(b = *p2)) {
137                    return (char *)cont;
138                }
139                else {
140                    if (toupper(*p1) == toupper(b)) {
141                        p1++;
142                        p2++;
143                    }
144                    else {
145                        p2 = substr;
146                        p1 = (++cont);
147                    }
148                }
149            }
150            if (!*p2) return (char *)cont;
151            break;
152        case 2: // a==a && a==?
153            for (p1 = cont, p2 = substr; *p1;) {
154                if (!(b = *p2)) {
155                    return (char *)cont;
156                }
157                else {
158                    if (b == *p1 || (b=='?')) {
159                        p1++;
160                        p2++;
161                    }
162                    else {
163                        p2 = substr;
164                        p1 = (++cont);
165                    }
166                }
167            }
168            if (!*p2) return (char *)cont;
169            break;
170
171        default: // a==A && a==?
172            for (p1 = cont, p2 = substr; *p1;) {
173                if (!(b = *p2)) {
174                    return (char *)cont;
175                }
176                else {
177                    if (toupper(*p1) == toupper(b) || (b=='?')) {
178                        p1++;
179                        p2++;
180                    }
181                    else {
182                        p2 = substr;
183                        p1 = (++cont);
184                    }
185                }
186            }
187            if (!*p2) return (char *)cont;
188            break;
189    }
190    return NULp;
191}
192
193bool GBS_string_matches(const char *str, const char *expr, GB_CASE case_sens) {
194    /* Wildcards in 'expr' string:
195     *      ?   one character
196     *      *   several characters
197     *
198     * if 'case_sens' == GB_IGNORE_CASE -> change all letters to uppercase
199     *
200     * returns true if strings are equal, false otherwise
201     */
202
203    const char *ps = str;
204    const char *pe = expr;
205
206    while (1) {
207        char s = *ps;
208        char e = *pe;
209
210        if (e == '*') {
211            if (!pe[1]) { // '*' at end of expression
212                break; // always match (even "nothing")
213            }
214
215            const char *nextStar = strchrnul(pe+1, '*');
216            int len = nextStar-pe-1; // part after '*' (and before EOS or next '*')
217            if (!nextStar[0]) { // no 2nd '*' found
218                // -> tail of string (if there is any) has to match
219                int psl = strlen(ps); // length of tail
220                if (psl<len) {    // str-tail shorter than expr-tail
221                    return false; // -> match impossible
222                }
223
224                ps += psl-len; // skip over characters expected to match the '*' (=goto str-tail)
225                ++pe;          // goto expr-tail
226            }
227            else { // found 2nd '*' -> search for string part between stars
228                {
229                    char *part = ARB_strpartdup(pe+1, nextStar-1);
230                    ps         = GBS_find_string(ps, part, 2+(case_sens == GB_IGNORE_CASE)); // match with '?' wildcard
231                    free(part);
232                }
233
234                if (!ps) {
235                    return false;
236                }
237                ps += len;
238                pe = nextStar;
239            }
240            continue;
241        }
242
243        if (!s) {
244            return !e;
245        }
246        if (s != e) {
247            if (e != '?') {
248                if (!e) {
249                    return !s;
250                }
251                if (case_sens == GB_IGNORE_CASE) {
252                    s = toupper(s);
253                    e = toupper(e);
254                    if (s != e) {
255                        return false;
256                    }
257                }
258                else {
259                    return false;
260                }
261            }
262        }
263        ps++;
264        pe++;
265    }
266    return true;
267}
268
269bool GBS_string_matches_regexp(const char *str, const GBS_string_matcher *expr) {
270    /* Wildcard or regular expression match
271     * Returns true if match
272     *
273     * Use GBS_compile_matcher() and GBS_free_matcher() to maintain 'expr'
274     */
275    bool matches = false;
276
277    switch (expr->type) {
278        case SM_ANY: {
279            matches = true;
280            break;
281        }
282        case SM_WILDCARDED: {
283            matches = GBS_string_matches(str, expr->wildexpr, expr->case_flag);
284            break;
285        }
286        case SM_REGEXPR: {
287            matches = GBS_regmatch_compiled(str, expr->regexpr, NULp);
288            break;
289        }
290        case SM_INVALID: {
291            gb_assert(0);
292            break;
293        }
294    }
295
296    return matches;
297}
298
299// -----------------------------------
300//      Search replace tool (SRT)
301
302#define GBS_SET   ((char)1)
303#define GBS_SEP   ((char)2)
304#define GBS_MWILD ((char)3)
305#define GBS_WILD  ((char)4)
306
307__ATTR__USERESULT static GB_ERROR gbs_build_replace_string(GBS_strstruct& out,
308                                                           char *replaceBy, // will be modified!
309                                                           const char       *sWildcards, long sWildMax,
310                                                           const char*const *mWildcards, long mWildMax,
311                                                           const GBL_call_env& callEnv)
312{
313    int sWildAuto = 0; // count plain occurrences of '?' in replace string (ie. w/o number behind)
314    int mWildAuto = 0; // same for '*'
315
316    GBDATA *gb_container = callEnv.get_ref();
317
318    char *p = replaceBy;
319    char  c;
320    while ((c=*(p++))) {
321        switch (c) {
322            case GBS_MWILD:
323            case GBS_WILD: {
324                char d = *(p++);
325                if (d=='(') { // "*(..)" expressions
326                    char *closingParen = search_matching_parenthesis(p);
327
328                    if (!closingParen) {
329                        return GBS_global_string("Unbalanced parenthesis in '%s'", p-1);
330                    }
331
332                    // found reference: "*(gbd)"
333                    int separator = 0;
334                    *closingParen = 0;
335                    char *psym = strpbrk(p, "#|:");
336                    if (psym) {
337                        separator = *psym;
338                        *psym = 0;
339                    }
340
341                    GBDATA *gb_entry = NULp;
342                    if (*p) { // key was specified
343                        if (!gb_container) {
344                            return GBS_global_string("can't read key '%s' (called w/o database item)", p);
345                        }
346                        if (!GB_is_container(gb_container)) {
347                            if (ARB_strBeginsWith(p, "../")) { // redirect search via parent
348                                p += 3;
349                                gb_container = GB_get_father(gb_container);
350                            }
351                            else {
352                                return GBS_global_string("can't read key '%s' (DB item is no container)", p);
353                            }
354                        }
355                        gb_entry = GB_search(gb_container, p, GB_FIND);
356                        if (!gb_entry && GB_have_error()) {
357                            return GB_await_error();
358                        }
359                    }
360                    else {
361                        gb_entry = gb_container;
362                    }
363
364                    if (psym) *psym = separator;
365
366                    char *entry = (gb_entry && gb_entry != gb_container)
367                        ? GB_read_as_string(gb_entry)
368                        : ARB_strdup("");
369
370                    if (entry) {
371                        char *h;
372                        switch (separator) {
373                            case ':':
374                                h = GBS_string_eval_in_env(entry, psym+1, callEnv);
375                                if (!h) {
376                                    free(entry);
377                                    return GB_await_error();
378                                }
379
380                                out.cat(h);
381                                free(h);
382                                break;
383
384                            case '|':
385                                h = GB_command_interpreter_in_env(entry, psym+1, callEnv);
386                                if (!h) {
387                                    free(entry);
388                                    return GB_await_error();
389                                }
390
391                                out.cat(h);
392                                free(h);
393                                break;
394
395                            case '#':
396                                if (!entry[0]) { // missing field or empty content
397                                    out.cat(psym+1);
398                                    break;
399                                }
400                                // fall-through
401                            default:
402                                out.cat(entry);
403                                break;
404                        }
405                        free(entry);
406                    }
407                    *closingParen = ')';
408                    p = closingParen+1;
409                }
410                else {
411                    int  wildcard_num       = d - '1';
412                    bool followed_by_number = wildcard_num>=0 && wildcard_num<=9; // @@@ in fact this will also accept ':'
413
414                    if (c == GBS_WILD) {
415                        if (!followed_by_number) { // char behind wildcard is not in [1-9]
416                            --p; // "put back" that character
417                            wildcard_num = sWildAuto++;
418                        }
419                        if (wildcard_num>=sWildMax) {
420                            out.put('?');
421                        }
422                        else {
423                            out.put(sWildcards[wildcard_num]);
424                        }
425                    }
426                    else {
427                        if (!followed_by_number) { // char behind wildcard is not in [1-9]
428                            --p; // "put back" that character
429                            wildcard_num = mWildAuto++;
430                        }
431                        if (wildcard_num>=mWildMax) {
432                            out.put('*');
433                        }
434                        else {
435                            out.cat(mWildcards[wildcard_num]);
436                        }
437                    }
438                }
439                break;
440            }
441            default:
442                out.put(c);
443                break;
444        }
445    }
446    return NULp;
447}
448
449static char *gbs_compress_command(const char *com) {
450    /* Prepare SRT.
451     *
452     * Replaces all
453     *   '=' by GBS_SET
454     *   ':' by GBS_SEP
455     *   '?' by GBS_WILD if followed by a number or '?'
456     *   '*' by GBS_MWILD  or '('
457     * \ is the escape character
458     */
459
460    char       *result = ARB_strdup(com);
461    char       *d      = result;
462    const char *s      = result;
463    char ch;
464
465    while ((ch = *(s++))) {
466        switch (ch) {
467            case '=':   *(d++) = GBS_SET; break;
468            case ':':   *(d++) = GBS_SEP; break;
469            case '?':   *(d++) = GBS_WILD; break;
470            case '*':   *(d++) = GBS_MWILD; break;
471            case '\\':
472                ch = *(s++); if (!ch) { s--; break; };
473                switch (ch) {
474                    case 'n':   *(d++) = '\n'; break;
475                    case 't':   *(d++) = '\t'; break;
476                    case '0':   *(d++) = '\0'; break;
477                    default:    *(d++) = ch; break;
478                }
479                break;
480
481            default: *(d++) = ch; break;
482        }
483    }
484    *d = 0;
485    return result;
486}
487
488// AISC_MKPT_PROMOTE: class GBL_call_env;
489
490char *GBS_string_eval_in_env(const char *insource, const char *icommand, const GBL_call_env& callEnv) {
491     /* GBS_string_eval_in_env replaces substrings in source (implements SRT)
492      * Syntax: command = "oliver=olli:peter=peti"
493      *
494      * Returns a heapcopy of result of replacement.
495      *
496      *         * is a wildcard for any number of characters
497      *         ? is a wildcard for exactly one character
498      *
499      * To reference the parts matched by wildcards on the left side of the '=' use '?' and '*',
500      * to reference in a particular order use
501      *         *1 to reference to the first occurrence of *
502      *         *2 ----------"-------- second ------"-------
503      *         ...
504      *         *9 ----------"-------- ninth -------"-------
505      *
506      * If the first and last characters of the search string are no '*' wildcards,
507      * then the replace is repeated as many times as possible.
508      *
509      * '\' is the escape character: e.g. \n is newline; '\\' is '\'; '\=' is '='; ....
510      *
511      * If the passed GBL_call_env refers to a database entry (which has to be of type GB_DB, i.e. has to be a container),
512      * fields of that container may be inserted using
513      *
514      *         *(arb_field)          is the value of the containers child entry 'arb_field'
515      *         *(arb_field#string)   value of the child entry 'arb_field' or 'string' (if that entry does not exist)
516      *         *(arb_field\:SRT)     runs SRT recursively on the value of the child entry 'arb_field'
517      *         *([arb_field]|ACI)    runs the ACI command interpreter on the value of the child entry 'arb_field' (or on an empty string)
518      *
519      * If an error occurs it returns NULp - in this case the error-message gets exported!
520      *
521      * Notes:
522      * - global interpreter (SRT+ACI+REG) is provided by GB_command_interpreter_in_env()
523      * - REG is provided by GBS_regreplace(), GBS_regmatch() and GBS_regmatch_compiled()
524      * - ACI is only provided via GB_command_interpreter_in_env()
525      */
526    if (!icommand || !icommand[0]) {
527        return ARB_strdup(insource);
528    }
529
530    if (traceACI) {
531        print_trace(GBS_global_string("SR: in='%s' cmd='%s':\n", insource, icommand));
532    }
533    LocallyModify<int> inc(traceIndent, traceIndent+1);
534
535    char *command = gbs_compress_command(icommand);
536
537    // copy insource (to allow to modify it)
538    size_t        inlen = strlen(insource);
539    GBS_strstruct in(inlen+1);
540    in.ncat(insource, inlen);
541
542    GBS_strstruct out(inlen+500);
543
544    GB_ERROR  error = NULp;
545    char     *next_subcmd;
546    for (char *subcmd = command; subcmd; subcmd = next_subcmd) { // loop over sub-commands
547        // search next subcommand (=pos behind next colon):
548        next_subcmd = strchr(subcmd, GBS_SEP);
549        if (next_subcmd) *(next_subcmd++) = 0;
550
551        if (!subcmd[0]) continue; // empty subcommand -> do nothing
552
553        // search for replace string:
554        char *replaceBy = strchr(subcmd+1, GBS_SET);
555        if (!replaceBy) {
556            error = GBS_global_string("SRT ERROR: no '=' found in command '%s' (position > %zi)", icommand, subcmd-command+1);
557            break;
558        }
559        *(replaceBy++) = 0;
560
561        GB_CSTR not_yet_copied = in.get_data(); // point into 'in' string (to not-yet-copied part)
562        out.erase();
563
564        if (in.empty() && subcmd[0] == GBS_MWILD && subcmd[1] == 0) {
565            // plain '*' shall also match an empty input string -> handle manually here
566            const char *empty = "";
567            error             = gbs_build_replace_string(out, replaceBy, NULp, 0, &empty, 1, callEnv);
568        }
569        else {
570            char  sWildcard[40];  // character  which matched vs one '?'
571            char *mWildcard[10];  // substrings which matched vs one '*'
572            long  sWildSeen = 0;  // number of '?' seen (on left side on subcommand)
573            long  mWildSeen = 0;  // number of '*' seen (on left side on subcommand)
574
575            bool match_failed = false;
576            for (GB_CSTR source = not_yet_copied; *source; ) { // loop over string
577                gb_assert(!match_failed);
578
579                char    *search      = subcmd;
580                GB_CSTR  start_match = NULp; // start of string that matches a wildcard (none yet)
581
582                char     c;
583                while (!match_failed && (c = *(search++))) { // match expression vs. string
584                    switch (c) {
585                        case GBS_MWILD: {
586                            if (!start_match) start_match = source;
587
588                            char *start_of_wildcard = search;
589                            if (!(c = *(search++))) {       // last character is a '*' wildcard -> expression matched
590                                mWildcard[mWildSeen++] = ARB_strdup(source);
591                                source                     = strchr(source, 0); // jump to EOS
592                                --search;
593                                break; // (effectively does exit while-loop)
594                            }
595                            // @@@ 'c' read in above if-condition is ignored if non-zero (got tests)
596
597                            while ((c=*(search++)) && c!=GBS_MWILD && c!=GBS_WILD) ; // search the next wildcardstring
598
599                            search--; // back one character
600                            *search = 0;
601
602                            char    what_wild_card = c;
603                            GB_CSTR p              = GBS_find_string(source, start_of_wildcard, 0);
604
605                            if (!p) match_failed = true; // string behind wildcard does not appear in input -> no match
606                            else {
607                                mWildcard[mWildSeen++] = ARB_strpartdup(source, p-1);
608                                source = p + strlen(start_of_wildcard);
609                                *search = what_wild_card;
610                            }
611                            break;
612                        }
613                        case GBS_WILD:
614                            if (!source[0]) match_failed = true; // '?' does not match "nothing" -> no match
615                            else {
616                                if (!start_match) start_match = source;
617                                sWildcard[sWildSeen++]        = *(source++);
618                            }
619                            break;
620
621                        default:
622                            if (start_match) {
623                                if (c != *(source++)) match_failed = true; // mismatch after '?' or after last '*'
624                            }
625                            else {
626                                char *buf1 = search-1;
627
628                                while ((c=*(search++)) && c != GBS_MWILD && c!=GBS_WILD) ;   // search the next wildcardstring
629
630                                search--;                                                    // back one character
631                                *search = 0;
632
633                                char    what_wild_card = c;
634                                GB_CSTR p              = GBS_find_string(source, buf1, 0);
635                                if (!p) {
636                                    // string infrontof wildcard (or EOS) not found -> no match
637                                    match_failed = true;
638                                }
639                                else  {
640                                    start_match = p;
641                                    source      = p + strlen(buf1);
642                                    *search     = what_wild_card;
643                                }
644
645                            }
646                            break;
647                    }
648                }
649
650                if (!match_failed) {
651                    /* now we got
652                     *
653                     * in:                 GBS_strstruct containing entire input string
654                     * source:             pointer to end of match   (inside 'in')
655                     * start_match:        pointer to start of match (inside 'in')
656                     * not_yet_copied:     pointer to the not-copied part of the input string
657                     * replaceBy:          the replace string
658                     */
659
660                    // now look for the replace string
661                    out.ncat(not_yet_copied, start_match-not_yet_copied);                                                                // concat part before the match
662                    error          = gbs_build_replace_string(out, replaceBy, sWildcard, sWildSeen, mWildcard, mWildSeen, callEnv);      // execute SRT command
663                    not_yet_copied = source;
664                }
665
666                for (long i = 0; i < mWildSeen; i++) {
667                    freenull(mWildcard[i]);
668                }
669                sWildSeen = 0;
670                mWildSeen = 0;
671
672                if (error || match_failed) break;
673            }
674        }
675
676        // Note: reached when left side expression didn't match input string
677        // (also reached when done with current sub-expression)
678        if (error) break;
679
680        out.cat(not_yet_copied); // cat the rest of the input
681
682        if (traceACI) {
683            print_trace(GBS_global_string("'%s' -> '%s'\n", in.get_data(), out.get_data()));
684        }
685
686        in.swap_content(out);
687    }
688    free(command);
689    if (error) {
690        GB_export_error(error);
691        return NULp;
692    }
693    return in.release();
694}
695
696char *GBS_string_eval(const char *insource, const char *icommand) {
697    GBL_env      env(NULp, NULp);
698    GBL_call_env callEnv(NULp, env);
699
700    return GBS_string_eval_in_env(insource, icommand, callEnv);
701}
702
703// --------------------------------------------------------------------------------
704
705#ifdef UNIT_TESTS
706#ifndef TEST_UNIT_H
707#include <test_unit.h>
708#endif
709
710#include <arb_strarray.h>
711
712static char *tokenMatchResults(const char *expr, GB_CASE caseDef, const char *tokenStr) {
713    ConstStrArray token;
714    GBT_split_string(token,tokenStr, ';');
715
716    GBS_string_matcher *matcher = GBS_compile_matcher(expr, caseDef);
717    for (int t = 0; token[t]; ++t) {
718        bool matched = GBS_string_matches_regexp(token[t], matcher);
719        token.replace(t, matched ? "1" : "0");
720    }
721    GBS_free_matcher(matcher);
722    return GBT_join_strings(token, 0);
723}
724
725
726#define TEST_MATCH_TOKENS(expr,caseDef,tokenStr,expected) do{           \
727        char *results = tokenMatchResults(expr, caseDef, tokenStr);     \
728        TEST_EXPECT_EQUAL(results, expected);                           \
729        free(results);                                                  \
730    }while(0)
731
732#define TEST_MATCH_TOKENS__BROKEN(expr,caseDef,tokenStr,expected,got) do{       \
733        char *results = tokenMatchResults(expr, caseDef, tokenStr);             \
734        TEST_EXPECT_EQUAL__BROKEN(results, expected, got);                      \
735        free(results);                                                          \
736    }while(0)
737
738void TEST_matcher() {
739    TEST_MATCH_TOKENS("???",  GB_MIND_CASE, "ab;abc;abcd", "010");  // only matches 2nd string
740    TEST_MATCH_TOKENS("???*", GB_MIND_CASE, "ab;abc;abcd", "011");  // match at least 3 characters
741    TEST_MATCH_TOKENS("?*",   GB_MIND_CASE, ";a;ab;abc",   "0111"); // match at least 1 character
742
743    TEST_MATCH_TOKENS("a*c", GB_MIND_CASE,   "ca;ab;abc;abC;ABC;abcd", "001000");
744    TEST_MATCH_TOKENS("a*c", GB_IGNORE_CASE, "ca;ab;abc;abC;ABC;abcd", "001110");
745
746    TEST_MATCH_TOKENS("a*c*a",   GB_MIND_CASE, "aca;aaacccaaa;a--c--a;acac;acaca;aba", "111010");
747    TEST_MATCH_TOKENS("a*c?d*c", GB_MIND_CASE, "acxdc;a--cxd--c;acxdcxdcxdc;acdcdcdc", "1110");
748
749    TEST_MATCH_TOKENS("???*.c",    GB_MIND_CASE, "ab.c;abc.c;abcd.c",                   "011");
750    TEST_MATCH_TOKENS("?b.*.c",    GB_MIND_CASE, "ab.c;ab..c;ab.x.c",                   "011");
751    TEST_MATCH_TOKENS("rere*erer", GB_MIND_CASE, "rerer;rererer;rerererer;rererererer", "0011");
752}
753
754TEST_PUBLISH(TEST_matcher);
755
756#endif // UNIT_TESTS
757
758// --------------------------------------------------------------------------------
759
Note: See TracBrowser for help on using the repository browser.