Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_regex_core.py4677 linesDownload Raw Back to regex
1#
2# Secret Labs' Regular Expression Engine core module
3#
4# Copyright (c) 1998-2001 by Secret Labs AB.  All rights reserved.
5#
6# This version of the SRE library can be redistributed under CNRI's
7# Python 1.6 license.  For any other use, please contact Secret Labs
8# AB (info@pythonware.com).
9#
10# Portions of this engine have been developed in cooperation with
11# CNRI.  Hewlett-Packard provided funding for 1.6 integration and
12# other compatibility work.
13#
14# 2010-01-16 mrab Python front-end re-written and extended
15
16import enum
17import string
18import unicodedata
19from collections import defaultdict
20
21from regex import _regex
22
23__all__ = ["A", "ASCII", "B", "BESTMATCH", "D", "DEBUG", "E", "ENHANCEMATCH",
24  "F", "FULLCASE", "I", "IGNORECASE", "L", "LOCALE", "M", "MULTILINE", "P",
25  "POSIX", "R", "REVERSE", "S", "DOTALL", "T", "TEMPLATE", "U", "UNICODE",
26  "V0", "VERSION0", "V1", "VERSION1", "W", "WORD", "X", "VERBOSE", "error",
27  "Scanner", "RegexFlag"]
28
29# The regex exception.
30class error(Exception):
31    """Exception raised for invalid regular expressions.
32
33    Attributes:
34
35        msg: The unformatted error message
36        pattern: The regular expression pattern
37        pos: The position in the pattern where compilation failed, or None
38        lineno: The line number where compilation failed, unless pos is None
39        colno: The column number where compilation failed, unless pos is None
40    """
41
42    def __init__(self, message, pattern=None, pos=None):
43        newline = '\n' if isinstance(pattern, str) else b'\n'
44        self.msg = message
45        self.pattern = pattern
46        self.pos = pos
47        if pattern is not None and pos is not None:
48            self.lineno = pattern.count(newline, 0, pos) + 1
49            self.colno = pos - pattern.rfind(newline, 0, pos)
50
51            message = "{} at position {}".format(message, pos)
52
53            if newline in pattern:
54                message += " (line {}, column {})".format(self.lineno,
55                  self.colno)
56
57        Exception.__init__(self, message)
58
59# The exception for when a positional flag has been turned on in the old
60# behaviour.
61class _UnscopedFlagSet(Exception):
62    pass
63
64# The exception for when parsing fails and we want to try something else.
65class ParseError(Exception):
66    pass
67
68# The exception for when there isn't a valid first set.
69class _FirstSetError(Exception):
70    pass
71
72# Flags.
73class RegexFlag(enum.IntFlag):
74    A = ASCII = 0x80          # Assume ASCII locale.
75    B = BESTMATCH = 0x1000    # Best fuzzy match.
76    D = DEBUG = 0x200         # Print parsed pattern.
77    E = ENHANCEMATCH = 0x8000 # Attempt to improve the fit after finding the first
78                              # fuzzy match.
79    F = FULLCASE = 0x4000     # Unicode full case-folding.
80    I = IGNORECASE = 0x2      # Ignore case.
81    L = LOCALE = 0x4          # Assume current 8-bit locale.
82    M = MULTILINE = 0x8       # Make anchors look for newline.
83    P = POSIX = 0x10000       # POSIX-style matching (leftmost longest).
84    R = REVERSE = 0x400       # Search backwards.
85    S = DOTALL = 0x10         # Make dot match newline.
86    U = UNICODE = 0x20        # Assume Unicode locale.
87    V0 = VERSION0 = 0x2000    # Old legacy behaviour.
88    V1 = VERSION1 = 0x100     # New enhanced behaviour.
89    W = WORD = 0x800          # Default Unicode word breaks.
90    X = VERBOSE = 0x40        # Ignore whitespace and comments.
91    T = TEMPLATE = 0x1        # Template (present because re module has it).
92
93    def __repr__(self):
94        if self._name_ is not None:
95            return 'regex.%s' % self._name_
96
97        value = self._value_
98        members = []
99        negative = value < 0
100
101        if negative:
102            value = ~value
103
104        for m in self.__class__:
105            if value & m._value_:
106                value &= ~m._value_
107                members.append('regex.%s' % m._name_)
108
109        if value:
110            members.append(hex(value))
111
112        res = '|'.join(members)
113
114        if negative:
115            if len(members) > 1:
116                res = '~(%s)' % res
117            else:
118                res = '~%s' % res
119
120        return res
121
122    __str__ = object.__str__
123
124# Put the flags into the module namespace. Being explicit here helps tools like
125# linters and IDEs understand the code better.
126ASCII = RegexFlag.ASCII
127BESTMATCH = RegexFlag.BESTMATCH
128DEBUG = RegexFlag.DEBUG
129DOTALL = RegexFlag.DOTALL
130ENHANCEMATCH = RegexFlag.ENHANCEMATCH
131FULLCASE = RegexFlag.FULLCASE
132IGNORECASE = RegexFlag.IGNORECASE
133LOCALE = RegexFlag.LOCALE
134MULTILINE = RegexFlag.MULTILINE
135POSIX = RegexFlag.POSIX
136REVERSE = RegexFlag.REVERSE
137TEMPLATE = RegexFlag.TEMPLATE
138UNICODE = RegexFlag.UNICODE
139VERBOSE = RegexFlag.VERBOSE
140VERSION0 = RegexFlag.VERSION0
141VERSION1 = RegexFlag.VERSION1
142WORD = RegexFlag.WORD
143A = RegexFlag.A
144B = RegexFlag.B
145D = RegexFlag.D
146E = RegexFlag.E
147F = RegexFlag.F
148I = RegexFlag.I
149L = RegexFlag.L
150M = RegexFlag.M
151P = RegexFlag.P
152R = RegexFlag.R
153S = RegexFlag.S
154U = RegexFlag.U
155V0 = RegexFlag.V0
156V1 = RegexFlag.V1
157W = RegexFlag.W
158X = RegexFlag.X
159T = RegexFlag.T
160
161DEFAULT_VERSION = VERSION1
162
163_ALL_VERSIONS = VERSION0 | VERSION1
164_ALL_ENCODINGS = ASCII | LOCALE | UNICODE
165
166# The default flags for the various versions.
167DEFAULT_FLAGS = {VERSION0: 0, VERSION1: FULLCASE}
168
169# The mask for the flags.
170GLOBAL_FLAGS = (_ALL_VERSIONS | BESTMATCH | DEBUG | ENHANCEMATCH | POSIX |
171  REVERSE)
172SCOPED_FLAGS = (FULLCASE | IGNORECASE | MULTILINE | DOTALL | WORD | VERBOSE |
173  _ALL_ENCODINGS)
174
175ALPHA = frozenset(string.ascii_letters)
176DIGITS = frozenset(string.digits)
177ALNUM = ALPHA | DIGITS
178OCT_DIGITS = frozenset(string.octdigits)
179HEX_DIGITS = frozenset(string.hexdigits)
180SPECIAL_CHARS = frozenset("()|?*+{^$.[\\#") | frozenset([""])
181NAMED_CHAR_PART = ALNUM | frozenset(" -")
182PROPERTY_NAME_PART = ALNUM | frozenset(" &_-.")
183SET_OPS = ("||", "~~", "&&", "--")
184
185# The width of the code words inside the regex engine.
186BYTES_PER_CODE = _regex.get_code_size()
187BITS_PER_CODE = BYTES_PER_CODE * 8
188
189# The repeat count which represents infinity.
190UNLIMITED = (1 << BITS_PER_CODE) - 1
191
192# The regular expression flags.
193REGEX_FLAGS = {"a": ASCII, "b": BESTMATCH, "e": ENHANCEMATCH, "f": FULLCASE,
194  "i": IGNORECASE, "L": LOCALE, "m": MULTILINE, "p": POSIX, "r": REVERSE,
195  "s": DOTALL, "u": UNICODE, "V0": VERSION0, "V1": VERSION1, "w": WORD, "x":
196  VERBOSE}
197
198# The case flags.
199CASE_FLAGS = FULLCASE | IGNORECASE
200NOCASE = 0
201FULLIGNORECASE = FULLCASE | IGNORECASE
202
203FULL_CASE_FOLDING = UNICODE | FULLIGNORECASE
204
205CASE_FLAGS_COMBINATIONS = {0: 0, FULLCASE: 0, IGNORECASE: IGNORECASE,
206  FULLIGNORECASE: FULLIGNORECASE}
207
208# The number of digits in hexadecimal escapes.
209HEX_ESCAPES = {"x": 2, "u": 4, "U": 8}
210
211# The names of the opcodes.
212OPCODES = """
213FAILURE
214SUCCESS
215ANY
216ANY_ALL
217ANY_ALL_REV
218ANY_REV
219ANY_U
220ANY_U_REV
221ATOMIC
222BOUNDARY
223BRANCH
224CALL_REF
225CHARACTER
226CHARACTER_IGN
227CHARACTER_IGN_REV
228CHARACTER_REV
229CONDITIONAL
230DEFAULT_BOUNDARY
231DEFAULT_END_OF_WORD
232DEFAULT_START_OF_WORD
233END
234END_OF_LINE
235END_OF_LINE_U
236END_OF_STRING
237END_OF_STRING_LINE
238END_OF_STRING_LINE_U
239END_OF_WORD
240FUZZY
241GRAPHEME_BOUNDARY
242GREEDY_REPEAT
243GROUP
244GROUP_CALL
245GROUP_EXISTS
246KEEP
247LAZY_REPEAT
248LOOKAROUND
249NEXT
250PROPERTY
251PROPERTY_IGN
252PROPERTY_IGN_REV
253PROPERTY_REV
254PRUNE
255RANGE
256RANGE_IGN
257RANGE_IGN_REV
258RANGE_REV
259REF_GROUP
260REF_GROUP_FLD
261REF_GROUP_FLD_REV
262REF_GROUP_IGN
263REF_GROUP_IGN_REV
264REF_GROUP_REV
265SEARCH_ANCHOR
266SET_DIFF
267SET_DIFF_IGN
268SET_DIFF_IGN_REV
269SET_DIFF_REV
270SET_INTER
271SET_INTER_IGN
272SET_INTER_IGN_REV
273SET_INTER_REV
274SET_SYM_DIFF
275SET_SYM_DIFF_IGN
276SET_SYM_DIFF_IGN_REV
277SET_SYM_DIFF_REV
278SET_UNION
279SET_UNION_IGN
280SET_UNION_IGN_REV
281SET_UNION_REV
282SKIP
283START_OF_LINE
284START_OF_LINE_U
285START_OF_STRING
286START_OF_WORD
287STRING
288STRING_FLD
289STRING_FLD_REV
290STRING_IGN
291STRING_IGN_REV
292STRING_REV
293FUZZY_EXT
294"""
295
296# Define the opcodes in a namespace.
297class Namespace:
298    pass
299
300OP = Namespace()
301for i, op in enumerate(OPCODES.split()):
302    setattr(OP, op, i)
303
304def _shrink_cache(cache_dict, args_dict, locale_sensitive, max_length, divisor=5):
305    """Make room in the given cache.
306
307    Args:
308        cache_dict: The cache dictionary to modify.
309        args_dict: The dictionary of named list args used by patterns.
310        max_length: Maximum # of entries in cache_dict before it is shrunk.
311        divisor: Cache will shrink to max_length - 1/divisor*max_length items.
312    """
313    # Toss out a fraction of the entries at random to make room for new ones.
314    # A random algorithm was chosen as opposed to simply cache_dict.popitem()
315    # as popitem could penalize the same regular expression repeatedly based
316    # on its internal hash value.  Being random should spread the cache miss
317    # love around.
318    cache_keys = tuple(cache_dict.keys())
319    overage = len(cache_keys) - max_length
320    if overage < 0:
321        # Cache is already within limits.  Normally this should not happen
322        # but it could due to multithreading.
323        return
324
325    number_to_toss = max_length // divisor + overage
326
327    # The import is done here to avoid a circular dependency.
328    import random
329    if not hasattr(random, 'sample'):
330        # Do nothing while resolving the circular dependency:
331        #  re->random->warnings->tokenize->string->re
332        return
333
334    for doomed_key in random.sample(cache_keys, number_to_toss):
335        try:
336            del cache_dict[doomed_key]
337        except KeyError:
338            # Ignore problems if the cache changed from another thread.
339            pass
340
341    # Rebuild the arguments and locale-sensitivity dictionaries.
342    args_dict.clear()
343    sensitivity_dict = {}
344    for pattern, pattern_type, flags, args, default_version, locale in tuple(cache_dict):
345        args_dict[pattern, pattern_type, flags, default_version, locale] = args
346        try:
347            sensitivity_dict[pattern_type, pattern] = locale_sensitive[pattern_type, pattern]
348        except KeyError:
349            pass
350
351    locale_sensitive.clear()
352    locale_sensitive.update(sensitivity_dict)
353
354def _fold_case(info, string):
355    "Folds the case of a string."
356    flags = info.flags
357    if (flags & _ALL_ENCODINGS) == 0:
358        flags |= info.guess_encoding
359
360    return _regex.fold_case(flags, string)
361
362def is_cased_i(info, char):
363    "Checks whether a character is cased."
364    return len(_regex.get_all_cases(info.flags, char)) > 1
365
366def is_cased_f(flags, char):
367    "Checks whether a character is cased."
368    return len(_regex.get_all_cases(flags, char)) > 1
369
370def _compile_firstset(info, fs):
371    "Compiles the firstset for the pattern."
372    reverse = bool(info.flags & REVERSE)
373    fs = _check_firstset(info, reverse, fs)
374    if not fs or isinstance(fs, AnyAll):
375        return []
376
377    # Compile the firstset.
378    return fs.compile(reverse)
379
380def _check_firstset(info, reverse, fs):
381    "Checks the firstset for the pattern."
382    if not fs or None in fs:
383        return None
384
385    # If we ignore the case, for simplicity we won't build a firstset.
386    members = set()
387    case_flags = NOCASE
388    for i in fs:
389        if isinstance(i, Character) and not i.positive:
390            return None
391
392#        if i.case_flags:
393#            if isinstance(i, Character):
394#                if is_cased_i(info, i.value):
395#                    return []
396#            elif isinstance(i, SetBase):
397#                return []
398        case_flags |= i.case_flags
399        members.add(i.with_flags(case_flags=NOCASE))
400
401    if case_flags == (FULLCASE | IGNORECASE):
402        return None
403
404    # Build the firstset.
405    fs = SetUnion(info, list(members), case_flags=case_flags & ~FULLCASE,
406      zerowidth=True)
407    fs = fs.optimise(info, reverse, in_set=True)
408
409    return fs
410
411def _flatten_code(code):
412    "Flattens the code from a list of tuples."
413    flat_code = []
414    for c in code:
415        flat_code.extend(c)
416
417    return flat_code
418
419def make_case_flags(info):
420    "Makes the case flags."
421    flags = info.flags & CASE_FLAGS
422
423    # Turn off FULLCASE if ASCII is turned on.
424    if info.flags & ASCII:
425        flags &= ~FULLCASE
426
427    return flags
428
429def make_character(info, value, in_set=False):
430    "Makes a character literal."
431    if in_set:
432        # A character set is built case-sensitively.
433        return Character(value)
434
435    return Character(value, case_flags=make_case_flags(info))
436
437def make_ref_group(info, name, position):
438    "Makes a group reference."
439    return RefGroup(info, name, position, case_flags=make_case_flags(info))
440
441def make_string_set(info, name):
442    "Makes a string set."
443    return StringSet(info, name, case_flags=make_case_flags(info))
444
445def make_property(info, prop, in_set):
446    "Makes a property."
447    if in_set:
448        return prop
449
450    return prop.with_flags(case_flags=make_case_flags(info))
451
452def _parse_pattern(source, info):
453    "Parses a pattern, eg. 'a|b|c'."
454    branches = [parse_sequence(source, info)]
455    while source.match("|"):
456        branches.append(parse_sequence(source, info))
457
458    if len(branches) == 1:
459        return branches[0]
460    return Branch(branches)
461
462def parse_sequence(source, info):
463    "Parses a sequence, eg. 'abc'."
464    sequence = [None]
465    case_flags = make_case_flags(info)
466    while True:
467        saved_pos = source.pos
468        ch = source.get()
469        if ch in SPECIAL_CHARS:
470            if ch in ")|":
471                # The end of a sequence. At the end of the pattern ch is "".
472                source.pos = saved_pos
473                break
474            elif ch == "\\":
475                # An escape sequence outside a set.
476                sequence.append(parse_escape(source, info, False))
477            elif ch == "(":
478                # A parenthesised subpattern or a flag.
479                element = parse_paren(source, info)
480                if element is None:
481                    case_flags = make_case_flags(info)
482                else:
483                    sequence.append(element)
484            elif ch == ".":
485                # Any character.
486                if info.flags & DOTALL:
487                    sequence.append(AnyAll())
488                elif info.flags & WORD:
489                    sequence.append(AnyU())
490                else:
491                    sequence.append(Any())
492            elif ch == "[":
493                # A character set.
494                sequence.append(parse_set(source, info))
495            elif ch == "^":
496                # The start of a line or the string.
497                if info.flags & MULTILINE:
498                    if info.flags & WORD:
499                        sequence.append(StartOfLineU())
500                    else:
501                        sequence.append(StartOfLine())
502                else:
503                    sequence.append(StartOfString())
504            elif ch == "$":
505                # The end of a line or the string.
506                if info.flags & MULTILINE:
507                    if info.flags & WORD:
508                        sequence.append(EndOfLineU())
509                    else:
510                        sequence.append(EndOfLine())
511                else:
512                    if info.flags & WORD:
513                        sequence.append(EndOfStringLineU())
514                    else:
515                        sequence.append(EndOfStringLine())
516            elif ch in "?*+{":
517                # Looks like a quantifier.
518                counts = parse_quantifier(source, info, ch)
519                if counts:
520                    # It _is_ a quantifier.
521                    apply_quantifier(source, info, counts, case_flags, ch,
522                      saved_pos, sequence)
523                    sequence.append(None)
524                else:
525                    # It's not a quantifier. Maybe it's a fuzzy constraint.
526                    constraints = parse_fuzzy(source, info, ch, case_flags)
527
528                    if constraints:
529                        # It _is_ a fuzzy constraint.
530                        if is_actually_fuzzy(constraints):
531                            apply_constraint(source, info, constraints, case_flags,
532                              saved_pos, sequence)
533                            sequence.append(None)
534                    else:
535                        # The element was just a literal.
536                        sequence.append(Character(ord(ch),
537                          case_flags=case_flags))
538            else:
539                # A literal.
540                sequence.append(Character(ord(ch), case_flags=case_flags))
541        else:
542            # A literal.
543            sequence.append(Character(ord(ch), case_flags=case_flags))
544
545    sequence = [item for item in sequence if item is not None]
546    return Sequence(sequence)
547
548def is_actually_fuzzy(constraints):
549    "Checks whether a fuzzy constraint is actually fuzzy."
550    if constraints.get("e") == (0, 0):
551        return False
552
553    if (constraints.get("s"), constraints.get("i"), constraints.get("d")) == ((0, 0), (0, 0), (0, 0)):
554        return False
555
556    return True
557
558def apply_quantifier(source, info, counts, case_flags, ch, saved_pos,
559  sequence):
560    element = sequence.pop()
561    if element is None:
562        if sequence:
563            raise error("multiple repeat", source.string, saved_pos)
564        raise error("nothing to repeat", source.string, saved_pos)
565
566    if isinstance(element, (GreedyRepeat, LazyRepeat, PossessiveRepeat)):
567        raise error("multiple repeat", source.string, saved_pos)
568
569    min_count, max_count = counts
570    saved_pos = source.pos
571    ch = source.get()
572    if ch == "?":
573        # The "?" suffix that means it's a lazy repeat.
574        repeated = LazyRepeat
575    elif ch == "+":
576        # The "+" suffix that means it's a possessive repeat.
577        repeated = PossessiveRepeat
578    else:
579        # No suffix means that it's a greedy repeat.
580        source.pos = saved_pos
581        repeated = GreedyRepeat
582
583    # Ignore the quantifier if it applies to a zero-width item or the number of
584    # repeats is fixed at 1.
585    if not element.is_empty() and (min_count != 1 or max_count != 1):
586        element = repeated(element, min_count, max_count)
587
588    sequence.append(element)
589
590def apply_constraint(source, info, constraints, case_flags, saved_pos,
591  sequence):
592    element = sequence.pop()
593    if element is None:
594        raise error("nothing for fuzzy constraint", source.string, saved_pos)
595
596    # If a group is marked as fuzzy then put all of the fuzzy part in the
597    # group.
598    if isinstance(element, Group):
599        element.subpattern = Fuzzy(element.subpattern, constraints)
600        sequence.append(element)
601    else:
602        sequence.append(Fuzzy(element, constraints))
603
604_QUANTIFIERS = {"?": (0, 1), "*": (0, None), "+": (1, None)}
605
606def parse_quantifier(source, info, ch):
607    "Parses a quantifier."
608    q = _QUANTIFIERS.get(ch)
609    if q:
610        # It's a quantifier.
611        return q
612
613    if ch == "{":
614        # Looks like a limited repeated element, eg. 'a{2,3}'.
615        counts = parse_limited_quantifier(source)
616        if counts:
617            return counts
618
619    return None
620
621def is_above_limit(count):
622    "Checks whether a count is above the maximum."
623    return count is not None and count >= UNLIMITED
624
625def parse_limited_quantifier(source):
626    "Parses a limited quantifier."
627    saved_pos = source.pos
628    min_count = parse_count(source)
629    if source.match(","):
630        max_count = parse_count(source)
631
632        # No minimum means 0 and no maximum means unlimited.
633        min_count = int(min_count or 0)
634        max_count = int(max_count) if max_count else None
635    else:
636        if not min_count:
637            source.pos = saved_pos
638            return None
639
640        min_count = max_count = int(min_count)
641
642    if not source.match ("}"):
643        source.pos = saved_pos
644        return None
645
646    if is_above_limit(min_count) or is_above_limit(max_count):
647        raise error("repeat count too big", source.string, saved_pos)
648
649    if max_count is not None and min_count > max_count:
650        raise error("min repeat greater than max repeat", source.string,
651          saved_pos)
652
653    return min_count, max_count
654
655def parse_fuzzy(source, info, ch, case_flags):
656    "Parses a fuzzy setting, if present."
657    saved_pos = source.pos
658
659    if ch != "{":
660        return None
661
662    constraints = {}
663    try:
664        parse_fuzzy_item(source, constraints)
665        while source.match(","):
666            parse_fuzzy_item(source, constraints)
667    except ParseError:
668        source.pos = saved_pos
669        return None
670
671    if source.match(":"):
672        constraints["test"] = parse_fuzzy_test(source, info, case_flags)
673
674    if not source.match("}"):
675        raise error("expected }", source.string, source.pos)
676
677    return constraints
678
679def parse_fuzzy_item(source, constraints):
680    "Parses a fuzzy setting item."
681    saved_pos = source.pos
682    try:
683        parse_cost_constraint(source, constraints)
684    except ParseError:
685        source.pos = saved_pos
686
687        parse_cost_equation(source, constraints)
688
689def parse_cost_constraint(source, constraints):
690    "Parses a cost constraint."
691    saved_pos = source.pos
692    ch = source.get()
693    if ch in ALPHA:
694        # Syntax: constraint [("<=" | "<") cost]
695        constraint = parse_constraint(source, constraints, ch)
696
697        max_inc = parse_fuzzy_compare(source)
698
699        if max_inc is None:
700            # No maximum cost.
701            constraints[constraint] = 0, None
702        else:
703            # There's a maximum cost.
704            cost_pos = source.pos
705            max_cost = parse_cost_limit(source)
706
707            # Inclusive or exclusive limit?
708            if not max_inc:
709                max_cost -= 1
710
711            if max_cost < 0:
712                raise error("bad fuzzy cost limit", source.string, cost_pos)
713
714            constraints[constraint] = 0, max_cost
715    elif ch in DIGITS:
716        # Syntax: cost ("<=" | "<") constraint ("<=" | "<") cost
717        source.pos = saved_pos
718
719        # Minimum cost.
720        cost_pos = source.pos
721        min_cost = parse_cost_limit(source)
722
723        min_inc = parse_fuzzy_compare(source)
724        if min_inc is None:
725            raise ParseError()
726
727        constraint = parse_constraint(source, constraints, source.get())
728
729        max_inc = parse_fuzzy_compare(source)
730        if max_inc is None:
731            raise ParseError()
732
733        # Maximum cost.
734        cost_pos = source.pos
735        max_cost = parse_cost_limit(source)
736
737        # Inclusive or exclusive limits?
738        if not min_inc:
739            min_cost += 1
740        if not max_inc:
741            max_cost -= 1
742
743        if not 0 <= min_cost <= max_cost:
744            raise error("bad fuzzy cost limit", source.string, cost_pos)
745
746        constraints[constraint] = min_cost, max_cost
747    else:
748        raise ParseError()
749
750def parse_cost_limit(source):
751    "Parses a cost limit."
752    cost_pos = source.pos
753    digits = parse_count(source)
754
755    try:
756        return int(digits)
757    except ValueError:
758        pass
759
760    raise error("bad fuzzy cost limit", source.string, cost_pos)
761
762def parse_constraint(source, constraints, ch):
763    "Parses a constraint."
764    if ch not in "deis":
765        raise ParseError()
766
767    if ch in constraints:
768        raise ParseError()
769
770    return ch
771
772def parse_fuzzy_compare(source):
773    "Parses a cost comparator."
774    if source.match("<="):
775        return True
776    elif source.match("<"):
777        return False
778    else:
779        return None
780
781def parse_cost_equation(source, constraints):
782    "Parses a cost equation."
783    if "cost" in constraints:
784        raise error("more than one cost equation", source.string, source.pos)
785
786    cost = {}
787
788    parse_cost_term(source, cost)
789    while source.match("+"):
790        parse_cost_term(source, cost)
791
792    max_inc = parse_fuzzy_compare(source)
793    if max_inc is None:
794        raise ParseError()
795
796    max_cost = int(parse_count(source))
797
798    if not max_inc:
799        max_cost -= 1
800
801    if max_cost < 0:
802        raise error("bad fuzzy cost limit", source.string, source.pos)
803
804    cost["max"] = max_cost
805
806    constraints["cost"] = cost
807
808def parse_cost_term(source, cost):
809    "Parses a cost equation term."
810    coeff = parse_count(source)
811    ch = source.get()
812    if ch not in "dis":
813        raise ParseError()
814
815    if ch in cost:
816        raise error("repeated fuzzy cost", source.string, source.pos)
817
818    cost[ch] = int(coeff or 1)
819
820def parse_fuzzy_test(source, info, case_flags):
821    saved_pos = source.pos
822    ch = source.get()
823    if ch in SPECIAL_CHARS:
824        if ch == "\\":
825            # An escape sequence outside a set.
826            return parse_escape(source, info, False)
827        elif ch == ".":
828            # Any character.
829            if info.flags & DOTALL:
830                return AnyAll()
831            elif info.flags & WORD:
832                return AnyU()
833            else:
834                return Any()
835        elif ch == "[":
836            # A character set.
837            return parse_set(source, info)
838        else:
839            raise error("expected character set", source.string, saved_pos)
840    elif ch:
841        # A literal.
842        return Character(ord(ch), case_flags=case_flags)
843    else:
844        raise error("expected character set", source.string, saved_pos)
845
846def parse_count(source):
847    "Parses a quantifier's count, which can be empty."
848    return source.get_while(DIGITS)
849
850def parse_paren(source, info):
851    """Parses a parenthesised subpattern or a flag. Returns FLAGS if it's an
852    inline flag.
853    """
854    saved_pos = source.pos
855    ch = source.get(True)
856    if ch == "?":
857        # (?...
858        saved_pos_2 = source.pos
859        ch = source.get(True)
860        if ch == "<":
861            # (?<...
862            saved_pos_3 = source.pos
863            ch = source.get()
864            if ch in ("=", "!"):
865                # (?<=... or (?<!...: lookbehind.
866                return parse_lookaround(source, info, True, ch == "=")
867
868            # (?<...: a named capture group.
869            source.pos = saved_pos_3
870            name = parse_name(source)
871            group = info.open_group(name)
872            source.expect(">")
873            saved_flags = info.flags
874            try:
875                subpattern = _parse_pattern(source, info)
876                source.expect(")")
877            finally:
878                info.flags = saved_flags
879                source.ignore_space = bool(info.flags & VERBOSE)
880
881            info.close_group()
882            return Group(info, group, subpattern)
883        if ch in ("=", "!"):
884            # (?=... or (?!...: lookahead.
885            return parse_lookaround(source, info, False, ch == "=")
886        if ch == "P":
887            # (?P...: a Python extension.
888            return parse_extension(source, info)
889        if ch == "#":
890            # (?#...: a comment.
891            return parse_comment(source)
892        if ch == "(":
893            # (?(...: a conditional subpattern.
894            return parse_conditional(source, info)
895        if ch == ">":
896            # (?>...: an atomic subpattern.
897            return parse_atomic(source, info)
898        if ch == "|":
899            # (?|...: a common/reset groups branch.
900            return parse_common(source, info)
901        if ch == "R" or "0" <= ch <= "9":
902            # (?R...: probably a call to a group.
903            return parse_call_group(source, info, ch, saved_pos_2)
904        if ch == "&":
905            # (?&...: a call to a named group.
906            return parse_call_named_group(source, info, saved_pos_2)
907        if (ch == "+" or ch == "-") and source.peek() in DIGITS:
908            return parse_rel_call_group(source, info, ch, saved_pos_2)
909
910        # (?...: probably a flags subpattern.
911        source.pos = saved_pos_2
912        return parse_flags_subpattern(source, info)
913
914    if ch == "*":
915        # (*...
916        saved_pos_2 = source.pos
917        word = source.get_while(set(")>"), include=False)
918        if word[ : 1].isalpha():
919            verb = VERBS.get(word)
920            if not verb:
921                raise error("unknown verb", source.string, saved_pos_2)
922
923            source.expect(")")
924
925            return verb
926
927    # (...: an unnamed capture group.
928    source.pos = saved_pos
929    group = info.open_group()
930    saved_flags = info.flags
931    try:
932        subpattern = _parse_pattern(source, info)
933        source.expect(")")
934    finally:
935        info.flags = saved_flags
936        source.ignore_space = bool(info.flags & VERBOSE)
937
938    info.close_group()
939
940    return Group(info, group, subpattern)
941
942def parse_extension(source, info):
943    "Parses a Python extension."
944    saved_pos = source.pos
945    ch = source.get()
946    if ch == "<":
947        # (?P<...: a named capture group.
948        name = parse_name(source)
949        group = info.open_group(name)
950        source.expect(">")
951        saved_flags = info.flags
952        try:
953            subpattern = _parse_pattern(source, info)
954            source.expect(")")
955        finally:
956            info.flags = saved_flags
957            source.ignore_space = bool(info.flags & VERBOSE)
958
959        info.close_group()
960
961        return Group(info, group, subpattern)
962    if ch == "=":
963        # (?P=...: a named group reference.
964        name = parse_name(source, allow_numeric=True)
965        source.expect(")")
966        if info.is_open_group(name):
967            raise error("cannot refer to an open group", source.string,
968              saved_pos)
969
970        return make_ref_group(info, name, saved_pos)
971    if ch == ">" or ch == "&":
972        # (?P>...: a call to a group.
973        return parse_call_named_group(source, info, saved_pos)
974
975    source.pos = saved_pos
976    raise error("unknown extension", source.string, saved_pos)
977
978def parse_comment(source):
979    "Parses a comment."
980    while True:
981        saved_pos = source.pos
982        c = source.get(True)
983
984        if not c or c == ")":
985            break
986
987        if c == "\\":
988            c = source.get(True)
989
990    source.pos = saved_pos
991    source.expect(")")
992
993    return None
994
995def parse_lookaround(source, info, behind, positive):
996    "Parses a lookaround."
997    saved_flags = info.flags
998    try:
999        subpattern = _parse_pattern(source, info)
1000        source.expect(")")
1001    finally:
1002        info.flags = saved_flags
1003        source.ignore_space = bool(info.flags & VERBOSE)
1004
1005    return LookAround(behind, positive, subpattern)
1006
1007def parse_conditional(source, info):
1008    "Parses a conditional subpattern."
1009    saved_flags = info.flags
1010    saved_pos = source.pos
1011    ch = source.get()
1012    if ch == "?":
1013        # (?(?...
1014        ch = source.get()
1015        if ch in ("=", "!"):
1016            # (?(?=... or (?(?!...: lookahead conditional.
1017            return parse_lookaround_conditional(source, info, False, ch == "=")
1018        if ch == "<":
1019            # (?(?<...
1020            ch = source.get()
1021            if ch in ("=", "!"):
1022                # (?(?<=... or (?(?<!...: lookbehind conditional.
1023                return parse_lookaround_conditional(source, info, True, ch ==
1024                  "=")
1025
1026        source.pos = saved_pos
1027        raise error("expected lookaround conditional", source.string,
1028          source.pos)
1029
1030    source.pos = saved_pos
1031    try:
1032        group = parse_name(source, True)
1033        source.expect(")")
1034        yes_branch = parse_sequence(source, info)
1035        if source.match("|"):
1036            no_branch = parse_sequence(source, info)
1037        else:
1038            no_branch = Sequence()
1039
1040        source.expect(")")
1041    finally:
1042        info.flags = saved_flags
1043        source.ignore_space = bool(info.flags & VERBOSE)
1044
1045    if yes_branch.is_empty() and no_branch.is_empty():
1046        return Sequence()
1047
1048    return Conditional(info, group, yes_branch, no_branch, saved_pos)
1049
1050def parse_lookaround_conditional(source, info, behind, positive):
1051    saved_flags = info.flags
1052    try:
1053        subpattern = _parse_pattern(source, info)
1054        source.expect(")")
1055    finally:
1056        info.flags = saved_flags
1057        source.ignore_space = bool(info.flags & VERBOSE)
1058
1059    yes_branch = parse_sequence(source, info)
1060    if source.match("|"):
1061        no_branch = parse_sequence(source, info)
1062    else:
1063        no_branch = Sequence()
1064
1065    source.expect(")")
1066
1067    return LookAroundConditional(behind, positive, subpattern, yes_branch,
1068      no_branch)
1069
1070def parse_atomic(source, info):
1071    "Parses an atomic subpattern."
1072    saved_flags = info.flags
1073    try:
1074        subpattern = _parse_pattern(source, info)
1075        source.expect(")")
1076    finally:
1077        info.flags = saved_flags
1078        source.ignore_space = bool(info.flags & VERBOSE)
1079
1080    return Atomic(subpattern)
1081
1082def parse_common(source, info):
1083    "Parses a common groups branch."
1084    # Capture group numbers in different branches can reuse the group numbers.
1085    initial_group_count = info.group_count
1086    branches = [parse_sequence(source, info)]
1087    final_group_count = info.group_count
1088    while source.match("|"):
1089        info.group_count = initial_group_count
1090        branches.append(parse_sequence(source, info))
1091        final_group_count = max(final_group_count, info.group_count)
1092
1093    info.group_count = final_group_count
1094    source.expect(")")
1095
1096    if len(branches) == 1:
1097        return branches[0]
1098    return Branch(branches)
1099
1100def parse_call_group(source, info, ch, pos):
1101    "Parses a call to a group."
1102    if ch == "R":
1103        group = "0"
1104    else:
1105        group = ch + source.get_while(DIGITS)
1106
1107    source.expect(")")
1108
1109    return CallGroup(info, group, pos)
1110
1111def parse_rel_call_group(source, info, ch, pos):
1112    "Parses a relative call to a group."
1113    digits = source.get_while(DIGITS)
1114    if not digits:
1115        raise error("missing relative group number", source.string, source.pos)
1116
1117    offset = int(digits)
1118    group = info.group_count + offset if ch == "+" else info.group_count - offset + 1
1119    if group <= 0:
1120        raise error("invalid relative group number", source.string, source.pos)
1121
1122    source.expect(")")
1123
1124    return CallGroup(info, group, pos)
1125
1126def parse_call_named_group(source, info, pos):
1127    "Parses a call to a named group."
1128    group = parse_name(source)
1129    source.expect(")")
1130
1131    return CallGroup(info, group, pos)
1132
1133def parse_flag_set(source):
1134    "Parses a set of inline flags."
1135    flags = 0
1136
1137    try:
1138        while True:
1139            saved_pos = source.pos
1140            ch = source.get()
1141            if ch == "V":
1142                ch += source.get()
1143            flags |= REGEX_FLAGS[ch]
1144    except KeyError:
1145        source.pos = saved_pos
1146
1147    return flags
1148
1149def parse_flags(source, info):
1150    "Parses flags being turned on/off."
1151    flags_on = parse_flag_set(source)
1152    if source.match("-"):
1153        flags_off = parse_flag_set(source)
1154        if not flags_off:
1155            raise error("bad inline flags: no flags after '-'", source.string,
1156              source.pos)
1157    else:
1158        flags_off = 0
1159
1160    if flags_on & LOCALE:
1161        # Remember that this pattern as an inline locale flag.
1162        info.inline_locale = True
1163
1164    return flags_on, flags_off
1165
1166def parse_subpattern(source, info, flags_on, flags_off):
1167    "Parses a subpattern with scoped flags."
1168    saved_flags = info.flags
1169    info.flags = (info.flags | flags_on) & ~flags_off
1170
1171    # Ensure that there aren't multiple encoding flags set.
1172    if info.flags & (ASCII | LOCALE | UNICODE):
1173        info.flags = (info.flags & ~_ALL_ENCODINGS) | flags_on
1174
1175    source.ignore_space = bool(info.flags & VERBOSE)
1176    try:
1177        subpattern = _parse_pattern(source, info)
1178        source.expect(")")
1179    finally:
1180        info.flags = saved_flags
1181        source.ignore_space = bool(info.flags & VERBOSE)
1182
1183    return subpattern
1184
1185def parse_flags_subpattern(source, info):
1186    """Parses a flags subpattern. It could be inline flags or a subpattern
1187    possibly with local flags. If it's a subpattern, then that's returned;
1188    if it's a inline flags, then None is returned.
1189    """
1190    flags_on, flags_off = parse_flags(source, info)
1191
1192    if flags_off & GLOBAL_FLAGS:
1193        raise error("bad inline flags: cannot turn off global flag",
1194          source.string, source.pos)
1195
1196    if flags_on & flags_off:
1197        raise error("bad inline flags: flag turned on and off", source.string,
1198          source.pos)
1199
1200    # Handle flags which are global in all regex behaviours.

Showing the first 1,200 of 4677 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai