CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
_regex_core.py4664 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                    if constraints:
528                        # It _is_ a fuzzy constraint.
529                        apply_constraint(source, info, constraints, case_flags,
530                          saved_pos, sequence)
531                        sequence.append(None)
532                    else:
533                        # The element was just a literal.
534                        sequence.append(Character(ord(ch),
535                          case_flags=case_flags))
536            else:
537                # A literal.
538                sequence.append(Character(ord(ch), case_flags=case_flags))
539        else:
540            # A literal.
541            sequence.append(Character(ord(ch), case_flags=case_flags))
542
543    sequence = [item for item in sequence if item is not None]
544    return Sequence(sequence)
545
546def apply_quantifier(source, info, counts, case_flags, ch, saved_pos,
547  sequence):
548    element = sequence.pop()
549    if element is None:
550        if sequence:
551            raise error("multiple repeat", source.string, saved_pos)
552        raise error("nothing to repeat", source.string, saved_pos)
553
554    if isinstance(element, (GreedyRepeat, LazyRepeat, PossessiveRepeat)):
555        raise error("multiple repeat", source.string, saved_pos)
556
557    min_count, max_count = counts
558    saved_pos = source.pos
559    ch = source.get()
560    if ch == "?":
561        # The "?" suffix that means it's a lazy repeat.
562        repeated = LazyRepeat
563    elif ch == "+":
564        # The "+" suffix that means it's a possessive repeat.
565        repeated = PossessiveRepeat
566    else:
567        # No suffix means that it's a greedy repeat.
568        source.pos = saved_pos
569        repeated = GreedyRepeat
570
571    # Ignore the quantifier if it applies to a zero-width item or the number of
572    # repeats is fixed at 1.
573    if not element.is_empty() and (min_count != 1 or max_count != 1):
574        element = repeated(element, min_count, max_count)
575
576    sequence.append(element)
577
578def apply_constraint(source, info, constraints, case_flags, saved_pos,
579  sequence):
580    element = sequence.pop()
581    if element is None:
582        raise error("nothing for fuzzy constraint", source.string, saved_pos)
583
584    # If a group is marked as fuzzy then put all of the fuzzy part in the
585    # group.
586    if isinstance(element, Group):
587        element.subpattern = Fuzzy(element.subpattern, constraints)
588        sequence.append(element)
589    else:
590        sequence.append(Fuzzy(element, constraints))
591
592_QUANTIFIERS = {"?": (0, 1), "*": (0, None), "+": (1, None)}
593
594def parse_quantifier(source, info, ch):
595    "Parses a quantifier."
596    q = _QUANTIFIERS.get(ch)
597    if q:
598        # It's a quantifier.
599        return q
600
601    if ch == "{":
602        # Looks like a limited repeated element, eg. 'a{2,3}'.
603        counts = parse_limited_quantifier(source)
604        if counts:
605            return counts
606
607    return None
608
609def is_above_limit(count):
610    "Checks whether a count is above the maximum."
611    return count is not None and count >= UNLIMITED
612
613def parse_limited_quantifier(source):
614    "Parses a limited quantifier."
615    saved_pos = source.pos
616    min_count = parse_count(source)
617    if source.match(","):
618        max_count = parse_count(source)
619
620        # No minimum means 0 and no maximum means unlimited.
621        min_count = int(min_count or 0)
622        max_count = int(max_count) if max_count else None
623    else:
624        if not min_count:
625            source.pos = saved_pos
626            return None
627
628        min_count = max_count = int(min_count)
629
630    if not source.match ("}"):
631        source.pos = saved_pos
632        return None
633
634    if is_above_limit(min_count) or is_above_limit(max_count):
635        raise error("repeat count too big", source.string, saved_pos)
636
637    if max_count is not None and min_count > max_count:
638        raise error("min repeat greater than max repeat", source.string,
639          saved_pos)
640
641    return min_count, max_count
642
643def parse_fuzzy(source, info, ch, case_flags):
644    "Parses a fuzzy setting, if present."
645    saved_pos = source.pos
646
647    if ch != "{":
648        return None
649
650    constraints = {}
651    try:
652        parse_fuzzy_item(source, constraints)
653        while source.match(","):
654            parse_fuzzy_item(source, constraints)
655    except ParseError:
656        source.pos = saved_pos
657        return None
658
659    if source.match(":"):
660        constraints["test"] = parse_fuzzy_test(source, info, case_flags)
661
662    if not source.match("}"):
663        raise error("expected }", source.string, source.pos)
664
665    return constraints
666
667def parse_fuzzy_item(source, constraints):
668    "Parses a fuzzy setting item."
669    saved_pos = source.pos
670    try:
671        parse_cost_constraint(source, constraints)
672    except ParseError:
673        source.pos = saved_pos
674
675        parse_cost_equation(source, constraints)
676
677def parse_cost_constraint(source, constraints):
678    "Parses a cost constraint."
679    saved_pos = source.pos
680    ch = source.get()
681    if ch in ALPHA:
682        # Syntax: constraint [("<=" | "<") cost]
683        constraint = parse_constraint(source, constraints, ch)
684
685        max_inc = parse_fuzzy_compare(source)
686
687        if max_inc is None:
688            # No maximum cost.
689            constraints[constraint] = 0, None
690        else:
691            # There's a maximum cost.
692            cost_pos = source.pos
693            max_cost = parse_cost_limit(source)
694
695            # Inclusive or exclusive limit?
696            if not max_inc:
697                max_cost -= 1
698
699            if max_cost < 0:
700                raise error("bad fuzzy cost limit", source.string, cost_pos)
701
702            constraints[constraint] = 0, max_cost
703    elif ch in DIGITS:
704        # Syntax: cost ("<=" | "<") constraint ("<=" | "<") cost
705        source.pos = saved_pos
706
707        # Minimum cost.
708        cost_pos = source.pos
709        min_cost = parse_cost_limit(source)
710
711        min_inc = parse_fuzzy_compare(source)
712        if min_inc is None:
713            raise ParseError()
714
715        constraint = parse_constraint(source, constraints, source.get())
716
717        max_inc = parse_fuzzy_compare(source)
718        if max_inc is None:
719            raise ParseError()
720
721        # Maximum cost.
722        cost_pos = source.pos
723        max_cost = parse_cost_limit(source)
724
725        # Inclusive or exclusive limits?
726        if not min_inc:
727            min_cost += 1
728        if not max_inc:
729            max_cost -= 1
730
731        if not 0 <= min_cost <= max_cost:
732            raise error("bad fuzzy cost limit", source.string, cost_pos)
733
734        constraints[constraint] = min_cost, max_cost
735    else:
736        raise ParseError()
737
738def parse_cost_limit(source):
739    "Parses a cost limit."
740    cost_pos = source.pos
741    digits = parse_count(source)
742
743    try:
744        return int(digits)
745    except ValueError:
746        pass
747
748    raise error("bad fuzzy cost limit", source.string, cost_pos)
749
750def parse_constraint(source, constraints, ch):
751    "Parses a constraint."
752    if ch not in "deis":
753        raise ParseError()
754
755    if ch in constraints:
756        raise ParseError()
757
758    return ch
759
760def parse_fuzzy_compare(source):
761    "Parses a cost comparator."
762    if source.match("<="):
763        return True
764    elif source.match("<"):
765        return False
766    else:
767        return None
768
769def parse_cost_equation(source, constraints):
770    "Parses a cost equation."
771    if "cost" in constraints:
772        raise error("more than one cost equation", source.string, source.pos)
773
774    cost = {}
775
776    parse_cost_term(source, cost)
777    while source.match("+"):
778        parse_cost_term(source, cost)
779
780    max_inc = parse_fuzzy_compare(source)
781    if max_inc is None:
782        raise ParseError()
783
784    max_cost = int(parse_count(source))
785
786    if not max_inc:
787        max_cost -= 1
788
789    if max_cost < 0:
790        raise error("bad fuzzy cost limit", source.string, source.pos)
791
792    cost["max"] = max_cost
793
794    constraints["cost"] = cost
795
796def parse_cost_term(source, cost):
797    "Parses a cost equation term."
798    coeff = parse_count(source)
799    ch = source.get()
800    if ch not in "dis":
801        raise ParseError()
802
803    if ch in cost:
804        raise error("repeated fuzzy cost", source.string, source.pos)
805
806    cost[ch] = int(coeff or 1)
807
808def parse_fuzzy_test(source, info, case_flags):
809    saved_pos = source.pos
810    ch = source.get()
811    if ch in SPECIAL_CHARS:
812        if ch == "\\":
813            # An escape sequence outside a set.
814            return parse_escape(source, info, False)
815        elif ch == ".":
816            # Any character.
817            if info.flags & DOTALL:
818                return AnyAll()
819            elif info.flags & WORD:
820                return AnyU()
821            else:
822                return Any()
823        elif ch == "[":
824            # A character set.
825            return parse_set(source, info)
826        else:
827            raise error("expected character set", source.string, saved_pos)
828    elif ch:
829        # A literal.
830        return Character(ord(ch), case_flags=case_flags)
831    else:
832        raise error("expected character set", source.string, saved_pos)
833
834def parse_count(source):
835    "Parses a quantifier's count, which can be empty."
836    return source.get_while(DIGITS)
837
838def parse_paren(source, info):
839    """Parses a parenthesised subpattern or a flag. Returns FLAGS if it's an
840    inline flag.
841    """
842    saved_pos = source.pos
843    ch = source.get(True)
844    if ch == "?":
845        # (?...
846        saved_pos_2 = source.pos
847        ch = source.get(True)
848        if ch == "<":
849            # (?<...
850            saved_pos_3 = source.pos
851            ch = source.get()
852            if ch in ("=", "!"):
853                # (?<=... or (?<!...: lookbehind.
854                return parse_lookaround(source, info, True, ch == "=")
855
856            # (?<...: a named capture group.
857            source.pos = saved_pos_3
858            name = parse_name(source)
859            group = info.open_group(name)
860            source.expect(">")
861            saved_flags = info.flags
862            try:
863                subpattern = _parse_pattern(source, info)
864                source.expect(")")
865            finally:
866                info.flags = saved_flags
867                source.ignore_space = bool(info.flags & VERBOSE)
868
869            info.close_group()
870            return Group(info, group, subpattern)
871        if ch in ("=", "!"):
872            # (?=... or (?!...: lookahead.
873            return parse_lookaround(source, info, False, ch == "=")
874        if ch == "P":
875            # (?P...: a Python extension.
876            return parse_extension(source, info)
877        if ch == "#":
878            # (?#...: a comment.
879            return parse_comment(source)
880        if ch == "(":
881            # (?(...: a conditional subpattern.
882            return parse_conditional(source, info)
883        if ch == ">":
884            # (?>...: an atomic subpattern.
885            return parse_atomic(source, info)
886        if ch == "|":
887            # (?|...: a common/reset groups branch.
888            return parse_common(source, info)
889        if ch == "R" or "0" <= ch <= "9":
890            # (?R...: probably a call to a group.
891            return parse_call_group(source, info, ch, saved_pos_2)
892        if ch == "&":
893            # (?&...: a call to a named group.
894            return parse_call_named_group(source, info, saved_pos_2)
895        if (ch == "+" or ch == "-") and source.peek() in DIGITS:
896            return parse_rel_call_group(source, info, ch, saved_pos_2)
897
898        # (?...: probably a flags subpattern.
899        source.pos = saved_pos_2
900        return parse_flags_subpattern(source, info)
901
902    if ch == "*":
903        # (*...
904        saved_pos_2 = source.pos
905        word = source.get_while(set(")>"), include=False)
906        if word[ : 1].isalpha():
907            verb = VERBS.get(word)
908            if not verb:
909                raise error("unknown verb", source.string, saved_pos_2)
910
911            source.expect(")")
912
913            return verb
914
915    # (...: an unnamed capture group.
916    source.pos = saved_pos
917    group = info.open_group()
918    saved_flags = info.flags
919    try:
920        subpattern = _parse_pattern(source, info)
921        source.expect(")")
922    finally:
923        info.flags = saved_flags
924        source.ignore_space = bool(info.flags & VERBOSE)
925
926    info.close_group()
927
928    return Group(info, group, subpattern)
929
930def parse_extension(source, info):
931    "Parses a Python extension."
932    saved_pos = source.pos
933    ch = source.get()
934    if ch == "<":
935        # (?P<...: a named capture group.
936        name = parse_name(source)
937        group = info.open_group(name)
938        source.expect(">")
939        saved_flags = info.flags
940        try:
941            subpattern = _parse_pattern(source, info)
942            source.expect(")")
943        finally:
944            info.flags = saved_flags
945            source.ignore_space = bool(info.flags & VERBOSE)
946
947        info.close_group()
948
949        return Group(info, group, subpattern)
950    if ch == "=":
951        # (?P=...: a named group reference.
952        name = parse_name(source, allow_numeric=True)
953        source.expect(")")
954        if info.is_open_group(name):
955            raise error("cannot refer to an open group", source.string,
956              saved_pos)
957
958        return make_ref_group(info, name, saved_pos)
959    if ch == ">" or ch == "&":
960        # (?P>...: a call to a group.
961        return parse_call_named_group(source, info, saved_pos)
962
963    source.pos = saved_pos
964    raise error("unknown extension", source.string, saved_pos)
965
966def parse_comment(source):
967    "Parses a comment."
968    while True:
969        saved_pos = source.pos
970        c = source.get(True)
971
972        if not c or c == ")":
973            break
974
975        if c == "\\":
976            c = source.get(True)
977
978    source.pos = saved_pos
979    source.expect(")")
980
981    return None
982
983def parse_lookaround(source, info, behind, positive):
984    "Parses a lookaround."
985    saved_flags = info.flags
986    try:
987        subpattern = _parse_pattern(source, info)
988        source.expect(")")
989    finally:
990        info.flags = saved_flags
991        source.ignore_space = bool(info.flags & VERBOSE)
992
993    return LookAround(behind, positive, subpattern)
994
995def parse_conditional(source, info):
996    "Parses a conditional subpattern."
997    saved_flags = info.flags
998    saved_pos = source.pos
999    ch = source.get()
1000    if ch == "?":
1001        # (?(?...
1002        ch = source.get()
1003        if ch in ("=", "!"):
1004            # (?(?=... or (?(?!...: lookahead conditional.
1005            return parse_lookaround_conditional(source, info, False, ch == "=")
1006        if ch == "<":
1007            # (?(?<...
1008            ch = source.get()
1009            if ch in ("=", "!"):
1010                # (?(?<=... or (?(?<!...: lookbehind conditional.
1011                return parse_lookaround_conditional(source, info, True, ch ==
1012                  "=")
1013
1014        source.pos = saved_pos
1015        raise error("expected lookaround conditional", source.string,
1016          source.pos)
1017
1018    source.pos = saved_pos
1019    try:
1020        group = parse_name(source, True)
1021        source.expect(")")
1022        yes_branch = parse_sequence(source, info)
1023        if source.match("|"):
1024            no_branch = parse_sequence(source, info)
1025        else:
1026            no_branch = Sequence()
1027
1028        source.expect(")")
1029    finally:
1030        info.flags = saved_flags
1031        source.ignore_space = bool(info.flags & VERBOSE)
1032
1033    if yes_branch.is_empty() and no_branch.is_empty():
1034        return Sequence()
1035
1036    return Conditional(info, group, yes_branch, no_branch, saved_pos)
1037
1038def parse_lookaround_conditional(source, info, behind, positive):
1039    saved_flags = info.flags
1040    try:
1041        subpattern = _parse_pattern(source, info)
1042        source.expect(")")
1043    finally:
1044        info.flags = saved_flags
1045        source.ignore_space = bool(info.flags & VERBOSE)
1046
1047    yes_branch = parse_sequence(source, info)
1048    if source.match("|"):
1049        no_branch = parse_sequence(source, info)
1050    else:
1051        no_branch = Sequence()
1052
1053    source.expect(")")
1054
1055    return LookAroundConditional(behind, positive, subpattern, yes_branch,
1056      no_branch)
1057
1058def parse_atomic(source, info):
1059    "Parses an atomic subpattern."
1060    saved_flags = info.flags
1061    try:
1062        subpattern = _parse_pattern(source, info)
1063        source.expect(")")
1064    finally:
1065        info.flags = saved_flags
1066        source.ignore_space = bool(info.flags & VERBOSE)
1067
1068    return Atomic(subpattern)
1069
1070def parse_common(source, info):
1071    "Parses a common groups branch."
1072    # Capture group numbers in different branches can reuse the group numbers.
1073    initial_group_count = info.group_count
1074    branches = [parse_sequence(source, info)]
1075    final_group_count = info.group_count
1076    while source.match("|"):
1077        info.group_count = initial_group_count
1078        branches.append(parse_sequence(source, info))
1079        final_group_count = max(final_group_count, info.group_count)
1080
1081    info.group_count = final_group_count
1082    source.expect(")")
1083
1084    if len(branches) == 1:
1085        return branches[0]
1086    return Branch(branches)
1087
1088def parse_call_group(source, info, ch, pos):
1089    "Parses a call to a group."
1090    if ch == "R":
1091        group = "0"
1092    else:
1093        group = ch + source.get_while(DIGITS)
1094
1095    source.expect(")")
1096
1097    return CallGroup(info, group, pos)
1098
1099def parse_rel_call_group(source, info, ch, pos):
1100    "Parses a relative call to a group."
1101    digits = source.get_while(DIGITS)
1102    if not digits:
1103        raise error("missing relative group number", source.string, source.pos)
1104
1105    offset = int(digits)
1106    group = info.group_count + offset if ch == "+" else info.group_count - offset + 1
1107    if group <= 0:
1108        raise error("invalid relative group number", source.string, source.pos)
1109
1110    source.expect(")")
1111
1112    return CallGroup(info, group, pos)
1113
1114def parse_call_named_group(source, info, pos):
1115    "Parses a call to a named group."
1116    group = parse_name(source)
1117    source.expect(")")
1118
1119    return CallGroup(info, group, pos)
1120
1121def parse_flag_set(source):
1122    "Parses a set of inline flags."
1123    flags = 0
1124
1125    try:
1126        while True:
1127            saved_pos = source.pos
1128            ch = source.get()
1129            if ch == "V":
1130                ch += source.get()
1131            flags |= REGEX_FLAGS[ch]
1132    except KeyError:
1133        source.pos = saved_pos
1134
1135    return flags
1136
1137def parse_flags(source, info):
1138    "Parses flags being turned on/off."
1139    flags_on = parse_flag_set(source)
1140    if source.match("-"):
1141        flags_off = parse_flag_set(source)
1142        if not flags_off:
1143            raise error("bad inline flags: no flags after '-'", source.string,
1144              source.pos)
1145    else:
1146        flags_off = 0
1147
1148    if flags_on & LOCALE:
1149        # Remember that this pattern as an inline locale flag.
1150        info.inline_locale = True
1151
1152    return flags_on, flags_off
1153
1154def parse_subpattern(source, info, flags_on, flags_off):
1155    "Parses a subpattern with scoped flags."
1156    saved_flags = info.flags
1157    info.flags = (info.flags | flags_on) & ~flags_off
1158
1159    # Ensure that there aren't multiple encoding flags set.
1160    if info.flags & (ASCII | LOCALE | UNICODE):
1161        info.flags = (info.flags & ~_ALL_ENCODINGS) | flags_on
1162
1163    source.ignore_space = bool(info.flags & VERBOSE)
1164    try:
1165        subpattern = _parse_pattern(source, info)
1166        source.expect(")")
1167    finally:
1168        info.flags = saved_flags
1169        source.ignore_space = bool(info.flags & VERBOSE)
1170
1171    return subpattern
1172
1173def parse_flags_subpattern(source, info):
1174    """Parses a flags subpattern. It could be inline flags or a subpattern
1175    possibly with local flags. If it's a subpattern, then that's returned;
1176    if it's a inline flags, then None is returned.
1177    """
1178    flags_on, flags_off = parse_flags(source, info)
1179
1180    if flags_off & GLOBAL_FLAGS:
1181        raise error("bad inline flags: cannot turn off global flag",
1182          source.string, source.pos)
1183
1184    if flags_on & flags_off:
1185        raise error("bad inline flags: flag turned on and off", source.string,
1186          source.pos)
1187
1188    # Handle flags which are global in all regex behaviours.
1189    new_global_flags = (flags_on & ~info.global_flags) & GLOBAL_FLAGS
1190    if new_global_flags:
1191        info.global_flags |= new_global_flags
1192
1193        # A global has been turned on, so reparse the pattern.
1194        raise _UnscopedFlagSet(info.global_flags)
1195
1196    # Ensure that from now on we have only scoped flags.
1197    flags_on &= ~GLOBAL_FLAGS
1198
1199    if source.match(":"):
1200        return parse_subpattern(source, info, flags_on, flags_off)

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

Aluode/PerceptionLabPortable · CoolFace