1/* 2 * re2glob - C implementation 3 * (c) 2007 ActiveState Software Inc. 4 */ 5 6#include <tcl.h> 7 8#define DEBUG 0 9 10static void 11ExpChopNested _ANSI_ARGS_ ((Tcl_UniChar** xstr, 12 int* xstrlen, 13 Tcl_UniChar open, 14 Tcl_UniChar close)); 15 16static Tcl_UniChar* 17ExpLiteral _ANSI_ARGS_ ((Tcl_UniChar* nexto, 18 Tcl_UniChar* str, 19 int strlen)); 20 21static Tcl_UniChar* 22ExpCollapseStar _ANSI_ARGS_ ((Tcl_UniChar* src, 23 Tcl_UniChar* last)); 24static Tcl_UniChar* 25ExpCollapseQForward _ANSI_ARGS_ ((Tcl_UniChar* src, 26 Tcl_UniChar* last)); 27 28static Tcl_UniChar* 29ExpCollapseQBack _ANSI_ARGS_ ((Tcl_UniChar* src, 30 Tcl_UniChar* last)); 31 32static Tcl_UniChar 33ExpBackslash _ANSI_ARGS_ ((char prefix, 34 Tcl_UniChar* str, 35 int strlen)); 36 37static int 38ExpCountStar _ANSI_ARGS_ ((Tcl_UniChar* src, Tcl_UniChar* last)); 39 40 41static char* 42xxx (Tcl_UniChar* x, int xl) 43{ 44 static Tcl_DString ds; 45 Tcl_DStringInit (&ds); 46 return Tcl_UniCharToUtfDString (x,xl,&ds); 47} 48 49 50Tcl_Obj* 51exp_retoglob ( 52 Tcl_UniChar* str, 53 int strlen) 54{ 55 /* 56 * Output: x2 size of input (literal where every character has to be 57 * quoted. 58 * Location: For next translated unit, in output. 59 * Size of last generated unit, in characters. 60 * Stack of output locations at opening parens. x1 size of input. 61 * Location for next location on stack. 62 */ 63 64 static Tcl_UniChar litprefix [] = {'*','*','*','='}; 65 static Tcl_UniChar areprefix [] = {'*','*','*',':'}; 66 static Tcl_UniChar areopts [] = {'(','?'}; 67 static Tcl_UniChar nocapture [] = {'?',':'}; 68 static Tcl_UniChar lookhas [] = {'?','='}; 69 static Tcl_UniChar looknot [] = {'?','!'}; 70 static Tcl_UniChar xcomment [] = {'?','#'}; 71 72 static Tcl_UniChar classa [] = {'[','.'}; 73 static Tcl_UniChar classb [] = {'[','='}; 74 static Tcl_UniChar classc [] = {'[',':'}; 75 76 77 int lastsz, expanded; 78 Tcl_UniChar* out; 79 Tcl_UniChar* nexto; 80 Tcl_UniChar** paren; 81 Tcl_UniChar** nextp; 82 Tcl_Obj* glob = NULL; 83 Tcl_UniChar* mark; 84 Tcl_UniChar ch; 85 86 /* 87 * Set things up. 88 */ 89 90 out = nexto = (Tcl_UniChar*) Tcl_Alloc (strlen*2*sizeof (Tcl_UniChar)); 91 paren = nextp = (Tcl_UniChar**) Tcl_Alloc (strlen* sizeof (Tcl_UniChar*)); 92 lastsz = -1; 93 expanded = 0; 94 95 /* 96 * Start processing ... 97 */ 98 99#define CHOP(n) {str += (n); strlen -= (n);} 100#define CHOPC(c) {while (*str != (c) && strlen) CHOP(1) ;} 101#define EMIT(c) {lastsz = 1; *nexto++ = (c);} 102#define EMITX(c) {lastsz++; *nexto++ = (c);} 103#define MATCH(lit) ((strlen >= (sizeof (lit)/sizeof (Tcl_UniChar))) && (0 == Tcl_UniCharNcmp (str,(lit),sizeof(lit)/sizeof (Tcl_UniChar)))) 104#define MATCHC(c) (strlen && (*str == (c))) 105#define PUSHPAREN {*nextp++ = nexto;} 106#define UNEMIT {nexto -= lastsz; lastsz = -1;} 107 /* Tcl_UniCharIsDigit ? */ 108#define MATCH_DIGIT (MATCHC ('0') || MATCHC ('1') || \ 109 MATCHC ('2') || MATCHC ('3') || \ 110 MATCHC ('4') || MATCHC ('5') || \ 111 MATCHC ('6') || MATCHC ('7') || \ 112 MATCHC ('8') || MATCHC ('9')) 113#define MATCH_HEXDIGIT (MATCH_DIGIT || \ 114 MATCHC ('a') || MATCHC ('A') || \ 115 MATCHC ('b') || MATCHC ('B') || \ 116 MATCHC ('c') || MATCHC ('C') || \ 117 MATCHC ('d') || MATCHC ('D') || \ 118 MATCHC ('e') || MATCHC ('E') || \ 119 MATCHC ('f') || MATCHC ('F')) 120#define EMITC(c) {if (((c) == '\\') || \ 121 ((c) == '*') || \ 122 ((c) == '?') || \ 123 ((c) == '$') || \ 124 ((c) == '^') || \ 125 ((c) == '[')) { \ 126 EMIT ('\\'); EMITX ((c)); \ 127 } else { \ 128 EMIT ((c));}} 129#define MATCH_AREOPTS(c) (c == 'b' || c == 'c' || \ 130 c == 'e' || c == 'i' || c == 'm' || c == 'n' || \ 131 c == 'p' || c == 'q' || c == 's' || c == 't' || \ 132 c == 'w' || c == 'x') 133 134#if DEBUG 135#define LOG if (1) fprintf 136#define FF fflush (stderr) 137#define MARK(s) LOG (stderr,#s "\n"); FF; 138#else 139#define LOG if (0) fprintf 140#define FF 141#define MARK(s) 142#endif 143 144 /* ***= -> literal string follows */ 145 146 LOG (stderr,"RE-2-GLOB '%s'\n", xxx(str,strlen)); FF; 147 148 if (MATCH (litprefix)) { 149 CHOP (4); 150 nexto = ExpLiteral (nexto, str, strlen); 151 goto done; 152 } 153 154 /* ***: -> RE is ARE. Always for Expect. Therefore ignore */ 155 156 if (MATCH (areprefix)) { 157 CHOP (4); 158 LOG (stderr,"ARE '%s'\n", xxx(str,strlen)); FF; 159 } 160 161 /* (?xyz) ARE options, in {bceimnpqstwx}. Not validating that the 162 * options are legal. We assume that the RE is valid. 163 */ 164 165 if (MATCH (areopts)) { /* "(?" */ 166 Tcl_UniChar* save = str; 167 Tcl_UniChar* stop; 168 int stoplen; 169 int save_strlen = strlen; 170 int all_ARE_opts = 1; 171 172 /* First, ensure that this is actually an ARE opts string. 173 * It could be something else (e.g., a non-capturing block). 174 */ 175 CHOP (2); 176 mark = str; CHOPC (')'); 177 stop = str; /* Remember closing parens location, allows */ 178 stoplen = strlen; /* us to avoid a second CHOPC run later */ 179 180 while (mark < str) { 181 if (MATCH_AREOPTS(*mark)) { 182 mark++; 183 } else { 184 all_ARE_opts = 0; 185 break; 186 } 187 } 188 189 /* Reset back to our entry point. */ 190 str = save; 191 strlen = save_strlen; 192 193 if (all_ARE_opts) { 194 /* Now actually perform the ARE option processing */ 195 LOG (stderr, "%s\n", "Processing AREOPTS"); FF; 196 197 CHOP (2); 198 mark = str; 199 /* Equivalent to CHOPC (')') */ 200 str = stop; 201 strlen = stoplen; 202 203 while (mark < str) { 204 if (*mark == 'q') { 205 CHOP (1); 206 nexto = ExpLiteral (nexto, str, strlen); 207 goto done; 208 } else if (*mark == 'x') { 209 expanded = 1; 210 LOG (stderr,"EXPANDED\n"); FF; 211 } 212 mark++; 213 } 214 CHOP (1); 215 } 216 } 217 218 while (strlen) { 219 220 LOG (stderr,"'%s' <-- ",xxx(out,nexto-out)); FF; 221 LOG (stderr,"'%s'\n", xxx(str,strlen)); FF; 222 223 if (expanded) { 224 /* Expanded syntax, whitespace and comments, ignore. */ 225 while (MATCHC (' ') || 226 MATCHC (0x9) || 227 MATCHC (0xa)) CHOP (1); 228 if (MATCHC ('#')) { 229 CHOPC (0xa); 230 if (strlen) CHOP (1); 231 continue; 232 } 233 } 234 235 if (MATCHC ('|')) { 236 /* branching is too complex */ 237 goto error; 238 } else if (MATCHC ('(')) { 239 /* open parens */ 240 CHOP (1); 241 if (MATCH (nocapture)) { /* "?:" */ 242 /* non capturing -save location */ 243 PUSHPAREN; 244 CHOP (2); 245 } else if (MATCH (lookhas) || /* "?=" */ 246 MATCH (looknot)) { /* "?!" */ 247 /* lookahead - ignore */ 248 CHOP (2); 249 ExpChopNested (&str, &strlen, '(', ')'); 250 } else if (MATCH (xcomment)) { /* "?#" */ 251 /* comment - ignore */ 252 CHOPC (')'); CHOP (1); 253 } else { 254 /* plain capturing */ 255 PUSHPAREN; 256 } 257 } else if (MATCHC (')')) { 258 /* Closing parens. */ 259 CHOP (1); 260 /* Everything coming after the saved result is new, and 261 * collapsed into a single entry for a possible coming operator 262 * to handle. 263 */ 264 nextp --; /* Back to last save */ 265 mark = *nextp; /* Location where generation for this parens started */ 266 lastsz = (nexto - mark); /* This many chars generated */ 267 /* Now lastsz has the correct value for a possibly following 268 * UNEMIT 269 */ 270 } else if (MATCHC ('$') || MATCHC ('^')) { 271 /* anchor constraints - ignore */ 272 CHOP (1); 273 } else if (MATCHC ('[')) { 274 /* Classes - reduce to any char [[=chars=]] [[.chars.]] 275 * [[:name:]] [chars] Count brackets to find end. 276 277 * These are a bit complicated ... [= =], [. .], [: {] sequences 278 * always have to be complete. '[' does NOT nest otherwise. And 279 * a ']' after the opening '[' (with only '^' allowed to 280 * intervene is a character, not the closing bracket. We have to 281 * process the class in pieces to handle all this. The Tcl level 282 * implementations (0-2 all have bugs one way or other, all 283 * different. 284 */ 285 286 int first = 1; 287 int allowed = 1; 288 CHOP (1); 289 while (strlen) { 290 if (first && MATCHC ('^')) { 291 /* ^ as first keeps allowed ok for one more cycle */ 292 CHOP (1); 293 first = 0; 294 continue; 295 } else if (allowed && MATCHC (']')) { 296 /* Not a closing bracket! */ 297 CHOP (1); 298 } else if (MATCHC (']')) { 299 /* Closing bracket found */ 300 CHOP (1); 301 break; 302 } else if (MATCH (classa) || 303 MATCH (classb) || 304 MATCH (classc)) { 305 Tcl_UniChar delim[2]; 306 delim[0] = str [1]; 307 delim[1] = ']'; 308 CHOP (2); 309 while (!MATCH (delim)) CHOP (1); 310 CHOP (2); 311 } else { 312 /* Any char in class */ 313 CHOP (1); 314 } 315 /* Reset flags handling start of class */ 316 allowed = first = 0; 317 } 318 319 EMIT ('?'); 320 } else if (MATCHC ('\\')) { 321 /* Escapes */ 322 CHOP (1); 323 if (MATCHC ('d') || MATCHC ('D') || 324 MATCHC ('s') || MATCHC ('S') || 325 MATCHC ('w') || MATCHC ('W')) { 326 /* Class shorthands - reduce to any char */ 327 EMIT ('?'); 328 CHOP (1); 329 } else if (MATCHC ('m') || MATCHC ('M') || 330 MATCHC ('y') || MATCHC ('Y') || 331 MATCHC ('A') || MATCHC ('Z')) { 332 /* constraint escapes - ignore */ 333 CHOP (1); 334 } else if (MATCHC ('B')) { 335 /* Backslash */ 336 EMIT ('\\'); 337 EMITX ('\\'); 338 CHOP (1); 339 } else if (MATCHC ('0')) { 340 /* Escape NULL */ 341 EMIT ('\0'); 342 CHOP (1); 343 } else if (MATCHC ('e')) { 344 /* Escape ESC */ 345 EMIT ('\033'); 346 CHOP (1); 347 } else if (MATCHC ('a')) { 348 /* Escape \a */ 349 EMIT (0x7); 350 CHOP (1); 351 } else if (MATCHC ('b')) { 352 /* Escape \b */ 353 EMIT (0x8); 354 CHOP (1); 355 } else if (MATCHC ('f')) { 356 /* Escape \f */ 357 EMIT (0xc); 358 CHOP (1); 359 } else if (MATCHC ('n')) { 360 /* Escape \n */ 361 EMIT (0xa); 362 CHOP (1); 363 } else if (MATCHC ('r')) { 364 /* Escape \r */ 365 EMIT (0xd); 366 CHOP (1); 367 } else if (MATCHC ('t')) { 368 /* Escape \t */ 369 EMIT (0x9); 370 CHOP (1); 371 } else if (MATCHC ('v')) { 372 /* Escape \v */ 373 EMIT (0xb); 374 CHOP (1); 375 } else if (MATCHC ('c') && (strlen >= 2)) { 376 /* Escape \cX - reduce to (.) */ 377 EMIT ('?'); 378 CHOP (2); 379 } else if (MATCHC ('x')) { 380 CHOP (1); 381 if (MATCH_HEXDIGIT) { 382 /* Escape hex character */ 383 mark = str; 384 while (MATCH_HEXDIGIT) CHOP (1); 385 if ((str - mark) > 2) { mark = str - 2; } 386 ch = ExpBackslash ('x',mark,str-mark); 387 EMITC (ch); 388 } else { 389 /* Without hex digits following this is a plain char */ 390 EMIT ('x'); 391 } 392 } else if (MATCHC ('u')) { 393 /* Escapes unicode short. */ 394 CHOP (1); 395 mark = str; 396 CHOP (4); 397 ch = ExpBackslash ('u',mark,str-mark); 398 EMITC (ch); 399 } else if (MATCHC ('U')) { 400 /* Escapes unicode long. */ 401 CHOP (1); 402 mark = str; 403 CHOP (8); 404 ch = ExpBackslash ('U',mark,str-mark); 405 EMITC (ch); 406 } else if (MATCH_DIGIT) { 407 /* Escapes, octal, and backreferences - reduce (.*) */ 408 CHOP (1); 409 while (MATCH_DIGIT) CHOP (1); 410 EMIT ('*'); 411 } else { 412 /* Plain escaped characters - copy over, requote */ 413 EMITC (*str); 414 CHOP (1); 415 } 416 } else if (MATCHC ('{')) { 417 /* Non-greedy and greedy bounds - reduce to (*) */ 418 CHOP (1); 419 if (MATCH_DIGIT) { 420 /* Locate closing brace and remove operator */ 421 CHOPC ('}'); CHOP (1); 422 /* Remove optional greedy quantifier */ 423 if (MATCHC ('?')) { CHOP (1);} 424 UNEMIT; 425 EMIT ('*'); 426 } else { 427 /* Brace is plain character, copy over */ 428 EMIT ('{'); 429 /* CHOP already done */ 430 } 431 } else if (MATCHC ('*') || 432 MATCHC ('+') || 433 MATCHC ('?')) { 434 /* (Non-)greedy operators - reduce to (*) */ 435 CHOP (1); 436 /* Remove optional greedy quantifier */ 437 if (MATCHC ('?')) { CHOP (1);} 438 UNEMIT; 439 EMIT ('*'); 440 } else if (MATCHC ('.')) { 441 /* anychar - copy over */ 442 EMIT ('?'); 443 CHOP (1); 444 } else { 445 /* Plain char, copy over. */ 446 EMIT (*str); 447 CHOP (1); 448 } 449 } 450 451 LOG (stderr,"'%s' <-- ",xxx(out,nexto-out)); FF; 452 LOG (stderr,"'%s'\n", xxx(str,strlen)); FF; 453 454 /* 455 * Clean up the output a bit (collapse *-sequences and absorb ?'s 456 * into adjacent *'s. 457 */ 458 459 MARK (QF) 460 nexto = ExpCollapseQForward (out,nexto); 461 LOG (stderr,"QF '%s'\n",xxx(out,nexto-out)); FF; 462 463 MARK (QB) 464 nexto = ExpCollapseQBack (out,nexto); 465 LOG (stderr,"QB '%s'\n",xxx(out,nexto-out)); FF; 466 467 MARK (QS) 468 nexto = ExpCollapseStar (out,nexto); 469 LOG (stderr,"ST '%s'\n",xxx(out,nexto-out)); FF; 470 471 /* 472 * Heuristic: if there are more than two *s, the risk is far too 473 * large that the result actually is slower than the normal re 474 * matching. So bail out. 475 */ 476 if (ExpCountStar (out,nexto) > 2) { 477 goto error; 478 } 479 480 /* 481 * Check if the result is actually useful. 482 * Empty or just a *, or ? are not. A series 483 * of ?'s is borderline, as they semi-count 484 * the buffer. 485 */ 486 487 if ((nexto == out) || 488 (((nexto-out) == 1) && 489 ((*out == '*') || 490 (*out == '?')))) { 491 goto error; 492 } 493 494 /* 495 * Result generation and cleanup. 496 */ 497 done: 498 LOG (stderr,"RESULT_ '%s'\n", xxx(out,nexto-out)); FF; 499 glob = Tcl_NewUnicodeObj (out,(nexto-out)); 500 goto cleanup; 501 502 error: 503 LOG (stderr,"RESULT_ ERROR\n"); FF; 504 505 cleanup: 506 Tcl_Free ((char*)out); 507 Tcl_Free ((char*)paren); 508 509 return glob; 510} 511 512static void 513#ifdef _AIX 514ExpChopNested (Tcl_UniChar** xstr, 515 int* xstrlen, 516 Tcl_UniChar open, 517 Tcl_UniChar close) 518#else 519ExpChopNested (xstr,xstrlen, open, close) 520 Tcl_UniChar** xstr; 521 int* xstrlen; 522 Tcl_UniChar open; 523 Tcl_UniChar close; 524#endif 525{ 526 Tcl_UniChar* str = *xstr; 527 int strlen = *xstrlen; 528 int level = 0; 529 530 while (strlen) { 531 if (MATCHC (open)) { 532 level ++; 533 } else if (MATCHC (close)) { 534 level --; 535 if (level < 0) { 536 CHOP (1); 537 break; 538 } 539 } 540 CHOP (1); 541 } 542 543 *xstr = str; 544 *xstrlen = strlen; 545} 546 547static Tcl_UniChar* 548ExpLiteral (nexto, str, strlen) 549 Tcl_UniChar* nexto; 550 Tcl_UniChar* str; 551 int strlen; 552{ 553 int lastsz; 554 555 LOG (stderr,"LITERAL '%s'\n", xxx(str,strlen)); FF; 556 557 while (strlen) { 558 EMITC (*str); 559 CHOP (1); 560 } 561 return nexto; 562} 563 564static Tcl_UniChar 565#ifdef _AIX 566ExpBackslash (char prefix, 567 Tcl_UniChar* str, 568 int strlen) 569#else 570ExpBackslash (prefix, str, strlen) 571 char prefix; 572 Tcl_UniChar* str; 573 int strlen; 574#endif 575{ 576 /* strlen <= 8 */ 577 char buf[20]; 578 char dst[TCL_UTF_MAX+1]; 579 Tcl_UniChar ch; 580 int at = 0; 581 582 /* Construct an utf backslash sequence we can throw to Tcl */ 583 584 buf [at++] = '\\'; 585 buf [at++] = prefix; 586 while (strlen) { 587 buf [at++] = *str++; 588 strlen --; 589 } 590 591 Tcl_UtfBackslash (buf, NULL, dst); 592 TclUtfToUniChar (dst, &ch); 593 return ch; 594} 595 596static Tcl_UniChar* 597ExpCollapseStar (src, last) 598 Tcl_UniChar* src; 599 Tcl_UniChar* last; 600{ 601 Tcl_UniChar* dst, *base; 602 int skip = 0; 603 int star = 0; 604 605 /* Collapses series of *'s into a single *. State machine. The 606 * complexity is due to the need of handling escaped characters. 607 */ 608 609 LOG (stderr,"Q-STAR\n"); FF; 610 611 for (dst = base = src; src < last;) { 612 613 LOG (stderr,"@%1d /%1d '%s' <-- ", star,skip,xxx(base,dst-base)); FF; 614 LOG (stderr,"'%s'\n", xxx(src,last-src)); FF; 615 616 if (skip) { 617 skip = 0; 618 star = 0; 619 } else if (*src == '\\') { 620 skip = 1; /* Copy next char, whatever its value */ 621 star = 0; 622 } else if (*src == '*') { 623 if (star) { 624 /* Previous char was *, do not copy the current * to collapse 625 * the sequence 626 */ 627 src++; 628 continue; 629 } 630 star = 1; /* *-series starts here */ 631 } else { 632 star = 0; 633 } 634 *dst++ = *src++; 635 } 636 637 LOG (stderr,"@%1d /%1d '%s' <-- ", star,skip,xxx(base,dst-base)); FF; 638 LOG (stderr,"'%s'\n", xxx(src,last-src)); FF; 639 640 return dst; 641} 642 643static Tcl_UniChar* 644ExpCollapseQForward (src, last) 645 Tcl_UniChar* src; 646 Tcl_UniChar* last; 647{ 648 Tcl_UniChar* dst, *base; 649 int skip = 0; 650 int quest = 0; 651 652 /* Collapses series of ?'s coming after a *. State machine. The 653 * complexity is due to the need of handling escaped characters. 654 */ 655 656 LOG (stderr,"Q-Forward\n"); FF; 657 658 for (dst = base = src; src < last;) { 659 660 LOG (stderr,"?%1d /%1d '%s' <-- ", quest,skip,xxx(base,dst-base)); FF; 661 LOG (stderr,"'%s'\n", xxx(src,last-src)); FF; 662 663 if (skip) { 664 skip = 0; 665 quest = 0; 666 } else if (*src == '\\') { 667 skip = 1; 668 quest = 0; 669 /* Copy next char, whatever its value */ 670 } else if (*src == '?') { 671 if (quest) { 672 /* Previous char was *, do not copy the current ? to collapse 673 * the sequence 674 */ 675 src++; 676 continue; 677 } 678 } else if (*src == '*') { 679 quest = 1; 680 } else { 681 quest = 0; 682 } 683 *dst++ = *src++; 684 } 685 686 LOG (stderr,"?%1d /%1d '%s' <-- ", quest,skip,xxx(base,dst-base)); FF; 687 LOG (stderr,"'%s'\n", xxx(src,last-src)); FF; 688 return dst; 689} 690 691static Tcl_UniChar* 692ExpCollapseQBack (src, last) 693 Tcl_UniChar* src; 694 Tcl_UniChar* last; 695{ 696 Tcl_UniChar* dst, *base; 697 int skip = 0; 698 699 /* Collapses series of ?'s coming before a *. State machine. The 700 * complexity is due to the need of handling escaped characters. 701 */ 702 703 LOG (stderr,"Q-Backward\n"); FF; 704 705 for (dst = base = src; src < last;) { 706 LOG (stderr,"/%1d '%s' <-- ",skip,xxx(base,dst-base)); FF; 707 LOG (stderr,"'%s'\n", xxx(src,last-src)); FF; 708 709 if (skip) { 710 skip = 0; 711 } else if (*src == '\\') { 712 skip = 1; 713 /* Copy next char, whatever its value */ 714 } else if (*src == '*') { 715 /* Move backward in the output while the previous character is 716 * an unescaped question mark. If there is a previous character, 717 * or a character before that.. 718 */ 719 720 while ((((dst-base) > 2) && (dst[-1] == '?') && (dst[-2] != '\\')) || 721 (((dst-base) == 1) && (dst[-1] == '?'))) { 722 dst --; 723 } 724 } 725 *dst++ = *src++; 726 } 727 728 LOG (stderr,"/%1d '%s' <-- \n",skip,xxx(base,dst-base)); FF; 729 LOG (stderr,"'%s'\n", xxx(src,last-src)); FF; 730 return dst; 731} 732 733static int 734ExpCountStar (src, last) 735 Tcl_UniChar* src; 736 Tcl_UniChar* last; 737{ 738 int skip = 0; 739 int stars = 0; 740 741 /* Count number of *'s. State machine. The complexity is due to the 742 * need of handling escaped characters. 743 */ 744 745 for (; src < last; src++) { 746 if (skip) { 747 skip = 0; 748 } else if (*src == '\\') { 749 skip = 1; 750 } else if (*src == '*') { 751 stars++; 752 } 753 } 754 755 return stars; 756} 757 758#undef CHOP 759#undef CHOPC 760#undef EMIT 761#undef EMITX 762#undef MATCH 763#undef MATCHC 764#undef MATCH_DIGIT 765#undef MATCH_HEXDIGIT 766#undef PUSHPAREN 767#undef UNEMIT 768