|
17 | 17 |
|
18 | 18 | import _sre |
19 | 19 | from . import _parser |
| 20 | +from ._casefix import _EXTRA_CASES |
20 | 21 | from ._constants import * |
21 | 22 |
|
22 | 23 | _CHARSET_ALL = [(NEGATE, None)] |
@@ -428,6 +429,319 @@ def _compile_info(code, pattern, flags): |
428 | 429 | _compile_charset(charset, flags, code) |
429 | 430 | code[skip] = len(code) - skip |
430 | 431 |
|
| 432 | + |
| 433 | +# --- Auto-possessification pass --------------------------------------------- |
| 434 | + |
| 435 | +_REPEAT_CODES = frozenset({MIN_REPEAT, MAX_REPEAT, POSSESSIVE_REPEAT}) |
| 436 | +_POSSESSIFY_UNITS = frozenset({LITERAL, NOT_LITERAL, ANY, ANY_ALL, IN, CATEGORY}) |
| 437 | + |
| 438 | +# \d, \w, \s and the line break category as unions of disjoint "atoms": |
| 439 | +# d=digit, l=word non-digit, b=line break, s=space non-line-break, o=other. |
| 440 | +# digit<=word, linebreak<=space and word disjoint from space hold for both |
| 441 | +# ASCII and Unicode, so disjoint atom sets mean really disjoint categories. |
| 442 | +_CAT_UNIVERSE = frozenset('dlbso') |
| 443 | +_CAT_ATOMS = { |
| 444 | + CATEGORY_DIGIT: frozenset('d'), |
| 445 | + CATEGORY_WORD: frozenset('dl'), |
| 446 | + CATEGORY_SPACE: frozenset('bs'), |
| 447 | + CATEGORY_LINEBREAK: frozenset('b'), |
| 448 | +} |
| 449 | +_CAT_ATOMS.update({ |
| 450 | + CATEGORY_NOT_DIGIT: _CAT_UNIVERSE - _CAT_ATOMS[CATEGORY_DIGIT], |
| 451 | + CATEGORY_NOT_WORD: _CAT_UNIVERSE - _CAT_ATOMS[CATEGORY_WORD], |
| 452 | + CATEGORY_NOT_SPACE: _CAT_UNIVERSE - _CAT_ATOMS[CATEGORY_SPACE], |
| 453 | + CATEGORY_NOT_LINEBREAK: _CAT_UNIVERSE - _CAT_ATOMS[CATEGORY_LINEBREAK], |
| 454 | +}) |
| 455 | + |
| 456 | +_ASCII_SPACE = frozenset(b' \t\n\r\f\v') |
| 457 | +_ASCII_WORD = frozenset(b'_') | frozenset( |
| 458 | + range(0x30, 0x3a)) | frozenset(range(0x41, 0x5b)) | frozenset(range(0x61, 0x7b)) |
| 459 | +_PROBE_LIMIT = 64 # cap on the size of a finite atom used as a witness set |
| 460 | + |
| 461 | +def _tolower(c, flags): |
| 462 | + if flags & SRE_FLAG_UNICODE: |
| 463 | + return _sre.unicode_tolower(c) |
| 464 | + return _sre.ascii_tolower(c) |
| 465 | + |
| 466 | +def _fold_set(c, flags): |
| 467 | + # Code points witnessing what LITERAL c matches: tolower() of any match |
| 468 | + # lies here and each element is itself a match (simple tolower plus |
| 469 | + # _EXTRA_CASES, like the IGNORECASE matcher). |
| 470 | + if not (flags & SRE_FLAG_IGNORECASE) or flags & SRE_FLAG_LOCALE: |
| 471 | + return (c,) |
| 472 | + lo = _tolower(c, flags) |
| 473 | + if flags & SRE_FLAG_UNICODE: |
| 474 | + extra = _EXTRA_CASES.get(lo) |
| 475 | + if extra: |
| 476 | + return (lo, *extra) |
| 477 | + return (lo,) |
| 478 | + |
| 479 | +def _lit_matches(d, c, flags): |
| 480 | + # Whether LITERAL d matches input code point c. |
| 481 | + if not (flags & SRE_FLAG_IGNORECASE) or flags & SRE_FLAG_LOCALE: |
| 482 | + return c == d |
| 483 | + return _tolower(c, flags) in _fold_set(d, flags) |
| 484 | + |
| 485 | +# Categories whose membership is invariant under case folding (verified over |
| 486 | +# the full range); the others cannot be decided under IGNORECASE, where a |
| 487 | +# charset member is matched against the lowercased character. |
| 488 | +_FOLD_CLOSED = frozenset({ |
| 489 | + CATEGORY_DIGIT, CATEGORY_WORD, CATEGORY_SPACE, CATEGORY_LINEBREAK, |
| 490 | + CATEGORY_NUMERIC, CATEGORY_PRINTABLE, CATEGORY_N, CATEGORY_LM, |
| 491 | + CATEGORY_NL, CATEGORY_NO, CATEGORY_CF, CATEGORY_Z, CATEGORY_ZS, |
| 492 | + CATEGORY_C, CATEGORY_CN, CATEGORY_XID_CONTINUE, CATEGORY_ASSIGNED, |
| 493 | + CATEGORY_BLANK, CATEGORY_GRAPH, CATEGORY_PRINT, CATEGORY_CASED, |
| 494 | +}) |
| 495 | +_FOLD_CLOSED |= frozenset(CH_NEGATE[cat] for cat in _FOLD_CLOSED) |
| 496 | + |
| 497 | +def _cat_matches(cat, c, flags): |
| 498 | + # Whether category cat matches code point c, decided by the engine's own |
| 499 | + # predicate; None if it depends on the runtime locale or on case folding. |
| 500 | + if flags & SRE_FLAG_LOCALE: |
| 501 | + return None |
| 502 | + if flags & SRE_FLAG_IGNORECASE and cat not in _FOLD_CLOSED: |
| 503 | + return None |
| 504 | + if flags & SRE_FLAG_UNICODE: |
| 505 | + cat = CH_UNICODE[cat] |
| 506 | + return _sre.category_matches(cat, c) |
| 507 | + |
| 508 | +def _member_matches(op, av, c, flags): |
| 509 | + # Whether a charset member (op, av) matches code point c. None if unknown. |
| 510 | + if op is LITERAL: |
| 511 | + return _lit_matches(av, c, flags) |
| 512 | + if op is RANGE: |
| 513 | + lo, hi = av |
| 514 | + if lo <= c <= hi: |
| 515 | + return True |
| 516 | + if not (flags & SRE_FLAG_IGNORECASE) or flags & SRE_FLAG_LOCALE: |
| 517 | + return False |
| 518 | + if lo <= _tolower(c, flags) <= hi or any(lo <= x <= hi |
| 519 | + for x in _fold_set(c, flags)): |
| 520 | + return True |
| 521 | + return None # case folding into the range can't be ruled out cheaply |
| 522 | + if op is CATEGORY: |
| 523 | + return _cat_matches(av, c, flags) |
| 524 | + return None |
| 525 | + |
| 526 | +def _atom_matches(op, av, c, flags): |
| 527 | + # Whether the one-character atom (op, av) matches code point c. |
| 528 | + # Returns None when it cannot be decided (callers treat that as "maybe"). |
| 529 | + if op is LITERAL: |
| 530 | + return _lit_matches(av, c, flags) |
| 531 | + if op is NOT_LITERAL: |
| 532 | + return not _lit_matches(av, c, flags) |
| 533 | + if op is CATEGORY: |
| 534 | + return _cat_matches(av, c, flags) |
| 535 | + if op is ANY: |
| 536 | + return True if flags & SRE_FLAG_DOTALL else c != 0x0a |
| 537 | + if op is ANY_ALL: |
| 538 | + return True |
| 539 | + if op is IN: |
| 540 | + # Evaluate the charset the way the engine's charset() walk does: |
| 541 | + # NEGATE toggles the polarity, a member hit returns the current |
| 542 | + # polarity, and the end returns the complement of the final one |
| 543 | + # (this also covers difference-fused charsets, see _fuse_difference). |
| 544 | + ok = True |
| 545 | + results = set() |
| 546 | + for iop, iav in av: |
| 547 | + if iop is NEGATE: |
| 548 | + ok = not ok |
| 549 | + continue |
| 550 | + r = _member_matches(iop, iav, c, flags) |
| 551 | + if r: |
| 552 | + results.add(ok) |
| 553 | + break |
| 554 | + if r is None: |
| 555 | + results.add(ok) # may or may not hit this member |
| 556 | + else: |
| 557 | + results.add(not ok) |
| 558 | + if len(results) == 1: |
| 559 | + return results.pop() |
| 560 | + return None |
| 561 | + return None |
| 562 | + |
| 563 | +def _finite_set(op, av, flags): |
| 564 | + # The set of code points the atom matches, if finite and small; else None. |
| 565 | + if op is LITERAL: |
| 566 | + return set(_fold_set(av, flags)) |
| 567 | + if op is IN: |
| 568 | + if av and av[0] == (NEGATE, None): |
| 569 | + return None |
| 570 | + out = set() |
| 571 | + for iop, iav in av: |
| 572 | + if iop is LITERAL: |
| 573 | + out.update(_fold_set(iav, flags)) |
| 574 | + elif iop is RANGE: |
| 575 | + if iav[1] - iav[0] >= _PROBE_LIMIT: |
| 576 | + return None |
| 577 | + for x in range(iav[0], iav[1] + 1): |
| 578 | + out.update(_fold_set(x, flags)) |
| 579 | + elif iop is CATEGORY: |
| 580 | + if flags & SRE_FLAG_LOCALE or flags & SRE_FLAG_UNICODE: |
| 581 | + return None # Unicode/locale categories are not small |
| 582 | + if iav is CATEGORY_DIGIT: |
| 583 | + out.update(range(0x30, 0x3a)) |
| 584 | + elif iav is CATEGORY_WORD: |
| 585 | + out.update(_ASCII_WORD) |
| 586 | + elif iav is CATEGORY_SPACE: |
| 587 | + out.update(_ASCII_SPACE) |
| 588 | + elif iav is CATEGORY_LINEBREAK: |
| 589 | + out.add(0x0a) |
| 590 | + else: |
| 591 | + return None # a negated ASCII category is not small |
| 592 | + else: |
| 593 | + return None |
| 594 | + if len(out) > _PROBE_LIMIT: |
| 595 | + return None |
| 596 | + return out |
| 597 | + return None |
| 598 | + |
| 599 | +def _cat_atom_set(op, av): |
| 600 | + # The dlbso atom set the atom matches, if it is a bare category or a |
| 601 | + # charset of categories (the first member claiming an atom decides it |
| 602 | + # with the current NEGATE polarity, the end claims the rest). |
| 603 | + if op is CATEGORY: |
| 604 | + return _CAT_ATOMS.get(av) |
| 605 | + if op is not IN: |
| 606 | + return None |
| 607 | + ok = True |
| 608 | + decided = set() |
| 609 | + matched = set() |
| 610 | + for iop, iav in av: |
| 611 | + if iop is NEGATE: |
| 612 | + ok = not ok |
| 613 | + continue |
| 614 | + if iop is not CATEGORY: |
| 615 | + if not ok: |
| 616 | + # a non-category member of a fail segment only narrows the |
| 617 | + # set; ignoring it over-approximates, which stays sound |
| 618 | + continue |
| 619 | + return None |
| 620 | + atoms = _CAT_ATOMS.get(iav) |
| 621 | + if atoms is None: |
| 622 | + return None |
| 623 | + if ok: |
| 624 | + matched |= atoms - decided |
| 625 | + decided |= atoms |
| 626 | + if not ok: |
| 627 | + matched |= _CAT_UNIVERSE - decided |
| 628 | + return matched |
| 629 | + |
| 630 | +def _as_single_category(op, av): |
| 631 | + # The category code if the atom is a bare category or a single-category |
| 632 | + # class, else None. |
| 633 | + if op is CATEGORY: |
| 634 | + return av |
| 635 | + if op is IN and len(av) == 1 and av[0][0] is CATEGORY: |
| 636 | + return av[0][1] |
| 637 | + return None |
| 638 | + |
| 639 | +def _disjoint(atom, other, flags): |
| 640 | + # True only if atom and other provably cannot match a common character. |
| 641 | + if flags & SRE_FLAG_LOCALE and flags & SRE_FLAG_IGNORECASE: |
| 642 | + # case folding is decided by the runtime locale; prove nothing |
| 643 | + return False |
| 644 | + ca = _as_single_category(*atom) |
| 645 | + if ca is not None: |
| 646 | + cb = _as_single_category(*other) |
| 647 | + # a category and its complement are disjoint whatever they mean |
| 648 | + if cb is not None and cb == CH_NEGATE[ca]: |
| 649 | + return True |
| 650 | + if not (flags & SRE_FLAG_LOCALE): |
| 651 | + a1 = _cat_atom_set(*atom) |
| 652 | + if a1 is not None: |
| 653 | + a2 = _cat_atom_set(*other) |
| 654 | + if a2 is not None and a1.isdisjoint(a2): |
| 655 | + return True |
| 656 | + fa = _finite_set(*atom, flags) |
| 657 | + fb = _finite_set(*other, flags) |
| 658 | + if fa is not None and fb is not None: |
| 659 | + return fa.isdisjoint(fb) |
| 660 | + if fa is not None: |
| 661 | + return not any(_atom_matches(*other, c, flags) is not False for c in fa) |
| 662 | + if fb is not None: |
| 663 | + return not any(_atom_matches(*atom, c, flags) is not False for c in fb) |
| 664 | + return False |
| 665 | + |
| 666 | +def _leading_atom(data): |
| 667 | + # The leading atom of a rigid body -- a concatenation of single-character |
| 668 | + # atoms with no internal choice. A repeat of it gives back only whole |
| 669 | + # iterations, so its leading atom is all the follower must avoid. |
| 670 | + lead = None |
| 671 | + for op, av in data: |
| 672 | + if op is SUBPATTERN and not av[1] and not av[2]: |
| 673 | + a = _leading_atom(av[3].data) |
| 674 | + elif op is ATOMIC_GROUP: |
| 675 | + a = _leading_atom(av.data) |
| 676 | + elif op in _POSSESSIFY_UNITS: |
| 677 | + a = (op, av) |
| 678 | + else: |
| 679 | + return None |
| 680 | + if a is None: |
| 681 | + return None |
| 682 | + if lead is None: |
| 683 | + lead = a |
| 684 | + return lead |
| 685 | + |
| 686 | +def _first_consumers(seq, i, flags, cont): |
| 687 | + # Atoms for every character that could be consumed at position i of seq; |
| 688 | + # cont is the same for what follows seq. None if it can't be analyzed. |
| 689 | + acc = [] |
| 690 | + n = len(seq) |
| 691 | + while i < n: |
| 692 | + op, av = seq[i] |
| 693 | + if op in _POSSESSIFY_UNITS: |
| 694 | + acc.append((op, av)) |
| 695 | + return acc |
| 696 | + if op is SUBPATTERN: |
| 697 | + if av[1] or av[2]: |
| 698 | + return None # flag-scoping group: atoms can't carry their flags |
| 699 | + after = _first_consumers(seq, i + 1, flags, cont) |
| 700 | + if after is None: |
| 701 | + return None |
| 702 | + inner = _first_consumers(av[3].data, 0, flags, after) |
| 703 | + return None if inner is None else acc + inner |
| 704 | + if op is ATOMIC_GROUP: |
| 705 | + after = _first_consumers(seq, i + 1, flags, cont) |
| 706 | + if after is None: |
| 707 | + return None |
| 708 | + inner = _first_consumers(av.data, 0, flags, after) |
| 709 | + return None if inner is None else acc + inner |
| 710 | + if op is BRANCH: |
| 711 | + after = _first_consumers(seq, i + 1, flags, cont) |
| 712 | + if after is None: |
| 713 | + return None |
| 714 | + for alt in av[1]: |
| 715 | + a = _first_consumers(alt.data, 0, flags, after) |
| 716 | + if a is None: |
| 717 | + return None |
| 718 | + acc += a |
| 719 | + return acc |
| 720 | + if op in _REPEAT_CODES: |
| 721 | + mn, mx, p = av |
| 722 | + sub = _first_consumers(p.data, 0, flags, None) |
| 723 | + if sub is None: |
| 724 | + return None |
| 725 | + acc += sub |
| 726 | + if mn == 0: |
| 727 | + i += 1 |
| 728 | + continue |
| 729 | + return acc |
| 730 | + if op is AT and av is AT_END_STRING: |
| 731 | + # \z matches only at the very end; backtracking the repeat moves |
| 732 | + # earlier and can never satisfy it, so nothing need be disjoint. |
| 733 | + return acc |
| 734 | + if op is AT and av is AT_END: |
| 735 | + # $ is like \z but also matches before a '\n'. Only MULTILINE |
| 736 | + # exposes an interior one to backtracking, and then only if the |
| 737 | + # repeat can match '\n'. |
| 738 | + if flags & SRE_FLAG_MULTILINE: |
| 739 | + return acc + [(LITERAL, 0x0a)] |
| 740 | + return acc |
| 741 | + return None # assertion, anchor, group reference, ... -> give up |
| 742 | + return None if cont is None else acc + cont |
| 743 | + |
| 744 | + |
431 | 745 | # Difference-fusion peephole: rewrite [A--B]-style A(?<![B]) into a single |
432 | 746 | # charset (see the engine's NEGATE polarity toggle). |
433 | 747 | def _subpatterns(op, av): |
@@ -493,17 +807,41 @@ def _fuse_difference(data): |
493 | 807 | out.append((op, av)) |
494 | 808 | data[:] = out |
495 | 809 |
|
496 | | -def _walk(seq): |
| 810 | +def _walk(seq, flags, cont): |
| 811 | + # Rewrite the sequence in place: fuse set-operation charsets (see |
| 812 | + # _fuse_branch and _fuse_difference) and turn greedy repeats possessive |
| 813 | + # where the repeated atom and every possible follower are disjoint. |
497 | 814 | for i, (op, av) in enumerate(seq): |
498 | | - for sub in _subpatterns(op, av): |
499 | | - _walk(sub.data) |
500 | | - if op is BRANCH: |
| 815 | + if op is SUBPATTERN: |
| 816 | + if av[1] or av[2]: |
| 817 | + # flag-scoping group: optimize inside it, but its boundary is |
| 818 | + # opaque since atoms there match under different flags |
| 819 | + _walk(av[3].data, _combine_flags(flags, av[1], av[2]), None) |
| 820 | + else: |
| 821 | + _walk(av[3].data, flags, |
| 822 | + _first_consumers(seq, i + 1, flags, cont)) |
| 823 | + elif op is BRANCH: |
| 824 | + after = _first_consumers(seq, i + 1, flags, cont) |
| 825 | + for alt in av[1]: |
| 826 | + _walk(alt.data, flags, after) |
501 | 827 | items = _fuse_branch(av) |
502 | 828 | if items is not None: |
503 | 829 | seq[i] = (IN, items) |
| 830 | + else: |
| 831 | + # ATOMIC_GROUP / ASSERT(_NOT) / GROUPREF_EXISTS / repeat body have an |
| 832 | + # opaque boundary -- optimize inside with no follower context. |
| 833 | + for sub in _subpatterns(op, av): |
| 834 | + _walk(sub.data, flags, None) |
| 835 | + if op is MAX_REPEAT: |
| 836 | + atom = _leading_atom(av[2].data) |
| 837 | + if atom is not None: |
| 838 | + follow = _first_consumers(seq, i + 1, flags, cont) |
| 839 | + if follow is not None and \ |
| 840 | + all(_disjoint(atom, f, flags) for f in follow): |
| 841 | + seq[i] = (POSSESSIVE_REPEAT, av) |
504 | 842 | _fuse_difference(seq) |
505 | 843 |
|
506 | | -def optimize(pattern): |
| 844 | +def optimize(pattern, flags): |
507 | 845 | """Rewrite a parsed pattern in place and return it.""" |
508 | | - _walk(pattern.data) |
| 846 | + _walk(pattern.data, flags, []) |
509 | 847 | return pattern |
0 commit comments