mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Merge pull request #5125 from qingshi163/sre-dev
Update Sre Engine Implementing to CPython 3.12
This commit is contained in:
14 files changed
+3107
-2455
No files matched your search
Generated
+5
-37
@@ -1422,34 +1422,13 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b"
|
||||
dependencies = [
|
||||
"num_enum_derive 0.5.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
dependencies = [
|
||||
"num_enum_derive 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.107",
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2191,7 +2170,7 @@ name = "rustpython-sre_engine"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"num_enum 0.7.2",
|
||||
"num_enum",
|
||||
"optional",
|
||||
]
|
||||
|
||||
@@ -2230,7 +2209,7 @@ dependencies = [
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"num_enum 0.7.2",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
@@ -2300,7 +2279,7 @@ dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"num_cpus",
|
||||
"num_enum 0.7.2",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"optional",
|
||||
"parking_lot",
|
||||
@@ -2319,10 +2298,10 @@ dependencies = [
|
||||
"rustpython-literal",
|
||||
"rustpython-parser",
|
||||
"rustpython-parser-core",
|
||||
"rustpython-sre_engine",
|
||||
"rustyline",
|
||||
"schannel",
|
||||
"serde",
|
||||
"sre-engine",
|
||||
"static_assertions",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
@@ -2550,17 +2529,6 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sre-engine"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a490c5c46c35dba9a6f5e7ee8e4d67e775eb2d2da0f115750b8d10e1c1ac2d28"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"num_enum 0.5.9",
|
||||
"optional",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
||||
Vendored
+134
-91
@@ -122,65 +122,40 @@ This module also defines an exception 'error'.
|
||||
"""
|
||||
|
||||
import enum
|
||||
import sre_compile
|
||||
import sre_parse
|
||||
from . import _compiler, _parser
|
||||
import functools
|
||||
try:
|
||||
import _locale
|
||||
except ImportError:
|
||||
_locale = None
|
||||
import _sre
|
||||
|
||||
|
||||
# public symbols
|
||||
__all__ = [
|
||||
"match", "fullmatch", "search", "sub", "subn", "split",
|
||||
"findall", "finditer", "compile", "purge", "template", "escape",
|
||||
"findall", "finditer", "compile", "purge", "escape",
|
||||
"error", "Pattern", "Match", "A", "I", "L", "M", "S", "X", "U",
|
||||
"ASCII", "IGNORECASE", "LOCALE", "MULTILINE", "DOTALL", "VERBOSE",
|
||||
"UNICODE",
|
||||
"UNICODE", "NOFLAG", "RegexFlag",
|
||||
]
|
||||
|
||||
__version__ = "2.2.1"
|
||||
|
||||
class RegexFlag(enum.IntFlag):
|
||||
ASCII = A = sre_compile.SRE_FLAG_ASCII # assume ascii "locale"
|
||||
IGNORECASE = I = sre_compile.SRE_FLAG_IGNORECASE # ignore case
|
||||
LOCALE = L = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale
|
||||
UNICODE = U = sre_compile.SRE_FLAG_UNICODE # assume unicode "locale"
|
||||
MULTILINE = M = sre_compile.SRE_FLAG_MULTILINE # make anchors look for newline
|
||||
DOTALL = S = sre_compile.SRE_FLAG_DOTALL # make dot match newline
|
||||
VERBOSE = X = sre_compile.SRE_FLAG_VERBOSE # ignore whitespace and comments
|
||||
@enum.global_enum
|
||||
@enum._simple_enum(enum.IntFlag, boundary=enum.KEEP)
|
||||
class RegexFlag:
|
||||
NOFLAG = 0
|
||||
ASCII = A = _compiler.SRE_FLAG_ASCII # assume ascii "locale"
|
||||
IGNORECASE = I = _compiler.SRE_FLAG_IGNORECASE # ignore case
|
||||
LOCALE = L = _compiler.SRE_FLAG_LOCALE # assume current 8-bit locale
|
||||
UNICODE = U = _compiler.SRE_FLAG_UNICODE # assume unicode "locale"
|
||||
MULTILINE = M = _compiler.SRE_FLAG_MULTILINE # make anchors look for newline
|
||||
DOTALL = S = _compiler.SRE_FLAG_DOTALL # make dot match newline
|
||||
VERBOSE = X = _compiler.SRE_FLAG_VERBOSE # ignore whitespace and comments
|
||||
# sre extensions (experimental, don't rely on these)
|
||||
TEMPLATE = T = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking
|
||||
DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation
|
||||
|
||||
def __repr__(self):
|
||||
if self._name_ is not None:
|
||||
return f're.{self._name_}'
|
||||
value = self._value_
|
||||
members = []
|
||||
negative = value < 0
|
||||
if negative:
|
||||
value = ~value
|
||||
for m in self.__class__:
|
||||
if value & m._value_:
|
||||
value &= ~m._value_
|
||||
members.append(f're.{m._name_}')
|
||||
if value:
|
||||
members.append(hex(value))
|
||||
res = '|'.join(members)
|
||||
if negative:
|
||||
if len(members) > 1:
|
||||
res = f'~({res})'
|
||||
else:
|
||||
res = f'~{res}'
|
||||
return res
|
||||
DEBUG = _compiler.SRE_FLAG_DEBUG # dump pattern after compilation
|
||||
__str__ = object.__str__
|
||||
|
||||
globals().update(RegexFlag.__members__)
|
||||
_numeric_repr_ = hex
|
||||
|
||||
# sre exception
|
||||
error = sre_compile.error
|
||||
error = _compiler.error
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# public interface
|
||||
@@ -200,16 +175,39 @@ def search(pattern, string, flags=0):
|
||||
a Match object, or None if no match was found."""
|
||||
return _compile(pattern, flags).search(string)
|
||||
|
||||
def sub(pattern, repl, string, count=0, flags=0):
|
||||
class _ZeroSentinel(int):
|
||||
pass
|
||||
_zero_sentinel = _ZeroSentinel()
|
||||
|
||||
def sub(pattern, repl, string, *args, count=_zero_sentinel, flags=_zero_sentinel):
|
||||
"""Return the string obtained by replacing the leftmost
|
||||
non-overlapping occurrences of the pattern in string by the
|
||||
replacement repl. repl can be either a string or a callable;
|
||||
if a string, backslash escapes in it are processed. If it is
|
||||
a callable, it's passed the Match object and must return
|
||||
a replacement string to be used."""
|
||||
return _compile(pattern, flags).sub(repl, string, count)
|
||||
if args:
|
||||
if count is not _zero_sentinel:
|
||||
raise TypeError("sub() got multiple values for argument 'count'")
|
||||
count, *args = args
|
||||
if args:
|
||||
if flags is not _zero_sentinel:
|
||||
raise TypeError("sub() got multiple values for argument 'flags'")
|
||||
flags, *args = args
|
||||
if args:
|
||||
raise TypeError("sub() takes from 3 to 5 positional arguments "
|
||||
"but %d were given" % (5 + len(args)))
|
||||
|
||||
def subn(pattern, repl, string, count=0, flags=0):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"'count' is passed as positional argument",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
|
||||
return _compile(pattern, flags).sub(repl, string, count)
|
||||
sub.__text_signature__ = '(pattern, repl, string, count=0, flags=0)'
|
||||
|
||||
def subn(pattern, repl, string, *args, count=_zero_sentinel, flags=_zero_sentinel):
|
||||
"""Return a 2-tuple containing (new_string, number).
|
||||
new_string is the string obtained by replacing the leftmost
|
||||
non-overlapping occurrences of the pattern in the source
|
||||
@@ -218,9 +216,28 @@ def subn(pattern, repl, string, count=0, flags=0):
|
||||
callable; if a string, backslash escapes in it are processed.
|
||||
If it is a callable, it's passed the Match object and must
|
||||
return a replacement string to be used."""
|
||||
return _compile(pattern, flags).subn(repl, string, count)
|
||||
if args:
|
||||
if count is not _zero_sentinel:
|
||||
raise TypeError("subn() got multiple values for argument 'count'")
|
||||
count, *args = args
|
||||
if args:
|
||||
if flags is not _zero_sentinel:
|
||||
raise TypeError("subn() got multiple values for argument 'flags'")
|
||||
flags, *args = args
|
||||
if args:
|
||||
raise TypeError("subn() takes from 3 to 5 positional arguments "
|
||||
"but %d were given" % (5 + len(args)))
|
||||
|
||||
def split(pattern, string, maxsplit=0, flags=0):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"'count' is passed as positional argument",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
|
||||
return _compile(pattern, flags).subn(repl, string, count)
|
||||
subn.__text_signature__ = '(pattern, repl, string, count=0, flags=0)'
|
||||
|
||||
def split(pattern, string, *args, maxsplit=_zero_sentinel, flags=_zero_sentinel):
|
||||
"""Split the source string by the occurrences of the pattern,
|
||||
returning a list containing the resulting substrings. If
|
||||
capturing parentheses are used in pattern, then the text of all
|
||||
@@ -228,7 +245,26 @@ def split(pattern, string, maxsplit=0, flags=0):
|
||||
list. If maxsplit is nonzero, at most maxsplit splits occur,
|
||||
and the remainder of the string is returned as the final element
|
||||
of the list."""
|
||||
if args:
|
||||
if maxsplit is not _zero_sentinel:
|
||||
raise TypeError("split() got multiple values for argument 'maxsplit'")
|
||||
maxsplit, *args = args
|
||||
if args:
|
||||
if flags is not _zero_sentinel:
|
||||
raise TypeError("split() got multiple values for argument 'flags'")
|
||||
flags, *args = args
|
||||
if args:
|
||||
raise TypeError("split() takes from 2 to 4 positional arguments "
|
||||
"but %d were given" % (4 + len(args)))
|
||||
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"'maxsplit' is passed as positional argument",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
|
||||
return _compile(pattern, flags).split(string, maxsplit)
|
||||
split.__text_signature__ = '(pattern, string, maxsplit=0, flags=0)'
|
||||
|
||||
def findall(pattern, string, flags=0):
|
||||
"""Return a list of all non-overlapping matches in the string.
|
||||
@@ -254,11 +290,9 @@ def compile(pattern, flags=0):
|
||||
def purge():
|
||||
"Clear the regular expression caches"
|
||||
_cache.clear()
|
||||
_compile_repl.cache_clear()
|
||||
_cache2.clear()
|
||||
_compile_template.cache_clear()
|
||||
|
||||
def template(pattern, flags=0):
|
||||
"Compile a template pattern, returning a Pattern object"
|
||||
return _compile(pattern, flags|T)
|
||||
|
||||
# SPECIAL_CHARS
|
||||
# closing ')', '}' and ']'
|
||||
@@ -277,60 +311,69 @@ def escape(pattern):
|
||||
pattern = str(pattern, 'latin1')
|
||||
return pattern.translate(_special_chars_map).encode('latin1')
|
||||
|
||||
Pattern = type(sre_compile.compile('', 0))
|
||||
Match = type(sre_compile.compile('', 0).match(''))
|
||||
Pattern = type(_compiler.compile('', 0))
|
||||
Match = type(_compiler.compile('', 0).match(''))
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# internals
|
||||
|
||||
_cache = {} # ordered!
|
||||
|
||||
# Use the fact that dict keeps the insertion order.
|
||||
# _cache2 uses the simple FIFO policy which has better latency.
|
||||
# _cache uses the LRU policy which has better hit rate.
|
||||
_cache = {} # LRU
|
||||
_cache2 = {} # FIFO
|
||||
_MAXCACHE = 512
|
||||
_MAXCACHE2 = 256
|
||||
assert _MAXCACHE2 < _MAXCACHE
|
||||
|
||||
def _compile(pattern, flags):
|
||||
# internal: compile pattern
|
||||
if isinstance(flags, RegexFlag):
|
||||
flags = flags.value
|
||||
try:
|
||||
return _cache[type(pattern), pattern, flags]
|
||||
return _cache2[type(pattern), pattern, flags]
|
||||
except KeyError:
|
||||
pass
|
||||
if isinstance(pattern, Pattern):
|
||||
if flags:
|
||||
raise ValueError(
|
||||
"cannot process flags argument with a compiled pattern")
|
||||
return pattern
|
||||
if not sre_compile.isstring(pattern):
|
||||
raise TypeError("first argument must be string or compiled pattern")
|
||||
p = sre_compile.compile(pattern, flags)
|
||||
if not (flags & DEBUG):
|
||||
|
||||
key = (type(pattern), pattern, flags)
|
||||
# Item in _cache should be moved to the end if found.
|
||||
p = _cache.pop(key, None)
|
||||
if p is None:
|
||||
if isinstance(pattern, Pattern):
|
||||
if flags:
|
||||
raise ValueError(
|
||||
"cannot process flags argument with a compiled pattern")
|
||||
return pattern
|
||||
if not _compiler.isstring(pattern):
|
||||
raise TypeError("first argument must be string or compiled pattern")
|
||||
p = _compiler.compile(pattern, flags)
|
||||
if flags & DEBUG:
|
||||
return p
|
||||
if len(_cache) >= _MAXCACHE:
|
||||
# Drop the oldest item
|
||||
# Drop the least recently used item.
|
||||
# next(iter(_cache)) is known to have linear amortized time,
|
||||
# but it is used here to avoid a dependency from using OrderedDict.
|
||||
# For the small _MAXCACHE value it doesn't make much of a difference.
|
||||
try:
|
||||
del _cache[next(iter(_cache))]
|
||||
except (StopIteration, RuntimeError, KeyError):
|
||||
pass
|
||||
_cache[type(pattern), pattern, flags] = p
|
||||
# Append to the end.
|
||||
_cache[key] = p
|
||||
|
||||
if len(_cache2) >= _MAXCACHE2:
|
||||
# Drop the oldest item.
|
||||
try:
|
||||
del _cache2[next(iter(_cache2))]
|
||||
except (StopIteration, RuntimeError, KeyError):
|
||||
pass
|
||||
_cache2[key] = p
|
||||
return p
|
||||
|
||||
@functools.lru_cache(_MAXCACHE)
|
||||
def _compile_repl(repl, pattern):
|
||||
def _compile_template(pattern, repl):
|
||||
# internal: compile replacement pattern
|
||||
return sre_parse.parse_template(repl, pattern)
|
||||
|
||||
def _expand(pattern, match, template):
|
||||
# internal: Match.expand implementation hook
|
||||
template = sre_parse.parse_template(template, pattern)
|
||||
return sre_parse.expand_template(template, match)
|
||||
|
||||
def _subx(pattern, template):
|
||||
# internal: Pattern.sub/subn implementation helper
|
||||
template = _compile_repl(template, pattern)
|
||||
if not template[0] and len(template[1]) == 1:
|
||||
# literal replacement
|
||||
return template[1][0]
|
||||
def filter(match, template=template):
|
||||
return sre_parse.expand_template(template, match)
|
||||
return filter
|
||||
return _sre.template(pattern, _parser.parse_template(repl, pattern))
|
||||
|
||||
# register myself for pickling
|
||||
|
||||
@@ -346,22 +389,22 @@ copyreg.pickle(Pattern, _pickle, _compile)
|
||||
|
||||
class Scanner:
|
||||
def __init__(self, lexicon, flags=0):
|
||||
from sre_constants import BRANCH, SUBPATTERN
|
||||
from ._constants import BRANCH, SUBPATTERN
|
||||
if isinstance(flags, RegexFlag):
|
||||
flags = flags.value
|
||||
self.lexicon = lexicon
|
||||
# combine phrases into a compound pattern
|
||||
p = []
|
||||
s = sre_parse.State()
|
||||
s = _parser.State()
|
||||
s.flags = flags
|
||||
for phrase, action in lexicon:
|
||||
gid = s.opengroup()
|
||||
p.append(sre_parse.SubPattern(s, [
|
||||
(SUBPATTERN, (gid, 0, 0, sre_parse.parse(phrase, flags))),
|
||||
p.append(_parser.SubPattern(s, [
|
||||
(SUBPATTERN, (gid, 0, 0, _parser.parse(phrase, flags))),
|
||||
]))
|
||||
s.closegroup(gid, p[-1])
|
||||
p = sre_parse.SubPattern(s, [(BRANCH, (None, p))])
|
||||
self.scanner = sre_compile.compile(p)
|
||||
p = _parser.SubPattern(s, [(BRANCH, (None, p))])
|
||||
self.scanner = _compiler.compile(p)
|
||||
def scan(self, string):
|
||||
result = []
|
||||
append = result.append
|
||||
Vendored
+106
@@ -0,0 +1,106 @@
|
||||
# Auto-generated by Tools/scripts/generate_re_casefix.py.
|
||||
|
||||
# Maps the code of lowercased character to codes of different lowercased
|
||||
# characters which have the same uppercase.
|
||||
_EXTRA_CASES = {
|
||||
# LATIN SMALL LETTER I: LATIN SMALL LETTER DOTLESS I
|
||||
0x0069: (0x0131,), # 'i': 'ı'
|
||||
# LATIN SMALL LETTER S: LATIN SMALL LETTER LONG S
|
||||
0x0073: (0x017f,), # 's': 'ſ'
|
||||
# MICRO SIGN: GREEK SMALL LETTER MU
|
||||
0x00b5: (0x03bc,), # 'µ': 'μ'
|
||||
# LATIN SMALL LETTER DOTLESS I: LATIN SMALL LETTER I
|
||||
0x0131: (0x0069,), # 'ı': 'i'
|
||||
# LATIN SMALL LETTER LONG S: LATIN SMALL LETTER S
|
||||
0x017f: (0x0073,), # 'ſ': 's'
|
||||
# COMBINING GREEK YPOGEGRAMMENI: GREEK SMALL LETTER IOTA, GREEK PROSGEGRAMMENI
|
||||
0x0345: (0x03b9, 0x1fbe), # '\u0345': 'ιι'
|
||||
# GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
|
||||
0x0390: (0x1fd3,), # 'ΐ': 'ΐ'
|
||||
# GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
|
||||
0x03b0: (0x1fe3,), # 'ΰ': 'ΰ'
|
||||
# GREEK SMALL LETTER BETA: GREEK BETA SYMBOL
|
||||
0x03b2: (0x03d0,), # 'β': 'ϐ'
|
||||
# GREEK SMALL LETTER EPSILON: GREEK LUNATE EPSILON SYMBOL
|
||||
0x03b5: (0x03f5,), # 'ε': 'ϵ'
|
||||
# GREEK SMALL LETTER THETA: GREEK THETA SYMBOL
|
||||
0x03b8: (0x03d1,), # 'θ': 'ϑ'
|
||||
# GREEK SMALL LETTER IOTA: COMBINING GREEK YPOGEGRAMMENI, GREEK PROSGEGRAMMENI
|
||||
0x03b9: (0x0345, 0x1fbe), # 'ι': '\u0345ι'
|
||||
# GREEK SMALL LETTER KAPPA: GREEK KAPPA SYMBOL
|
||||
0x03ba: (0x03f0,), # 'κ': 'ϰ'
|
||||
# GREEK SMALL LETTER MU: MICRO SIGN
|
||||
0x03bc: (0x00b5,), # 'μ': 'µ'
|
||||
# GREEK SMALL LETTER PI: GREEK PI SYMBOL
|
||||
0x03c0: (0x03d6,), # 'π': 'ϖ'
|
||||
# GREEK SMALL LETTER RHO: GREEK RHO SYMBOL
|
||||
0x03c1: (0x03f1,), # 'ρ': 'ϱ'
|
||||
# GREEK SMALL LETTER FINAL SIGMA: GREEK SMALL LETTER SIGMA
|
||||
0x03c2: (0x03c3,), # 'ς': 'σ'
|
||||
# GREEK SMALL LETTER SIGMA: GREEK SMALL LETTER FINAL SIGMA
|
||||
0x03c3: (0x03c2,), # 'σ': 'ς'
|
||||
# GREEK SMALL LETTER PHI: GREEK PHI SYMBOL
|
||||
0x03c6: (0x03d5,), # 'φ': 'ϕ'
|
||||
# GREEK BETA SYMBOL: GREEK SMALL LETTER BETA
|
||||
0x03d0: (0x03b2,), # 'ϐ': 'β'
|
||||
# GREEK THETA SYMBOL: GREEK SMALL LETTER THETA
|
||||
0x03d1: (0x03b8,), # 'ϑ': 'θ'
|
||||
# GREEK PHI SYMBOL: GREEK SMALL LETTER PHI
|
||||
0x03d5: (0x03c6,), # 'ϕ': 'φ'
|
||||
# GREEK PI SYMBOL: GREEK SMALL LETTER PI
|
||||
0x03d6: (0x03c0,), # 'ϖ': 'π'
|
||||
# GREEK KAPPA SYMBOL: GREEK SMALL LETTER KAPPA
|
||||
0x03f0: (0x03ba,), # 'ϰ': 'κ'
|
||||
# GREEK RHO SYMBOL: GREEK SMALL LETTER RHO
|
||||
0x03f1: (0x03c1,), # 'ϱ': 'ρ'
|
||||
# GREEK LUNATE EPSILON SYMBOL: GREEK SMALL LETTER EPSILON
|
||||
0x03f5: (0x03b5,), # 'ϵ': 'ε'
|
||||
# CYRILLIC SMALL LETTER VE: CYRILLIC SMALL LETTER ROUNDED VE
|
||||
0x0432: (0x1c80,), # 'в': 'ᲀ'
|
||||
# CYRILLIC SMALL LETTER DE: CYRILLIC SMALL LETTER LONG-LEGGED DE
|
||||
0x0434: (0x1c81,), # 'д': 'ᲁ'
|
||||
# CYRILLIC SMALL LETTER O: CYRILLIC SMALL LETTER NARROW O
|
||||
0x043e: (0x1c82,), # 'о': 'ᲂ'
|
||||
# CYRILLIC SMALL LETTER ES: CYRILLIC SMALL LETTER WIDE ES
|
||||
0x0441: (0x1c83,), # 'с': 'ᲃ'
|
||||
# CYRILLIC SMALL LETTER TE: CYRILLIC SMALL LETTER TALL TE, CYRILLIC SMALL LETTER THREE-LEGGED TE
|
||||
0x0442: (0x1c84, 0x1c85), # 'т': 'ᲄᲅ'
|
||||
# CYRILLIC SMALL LETTER HARD SIGN: CYRILLIC SMALL LETTER TALL HARD SIGN
|
||||
0x044a: (0x1c86,), # 'ъ': 'ᲆ'
|
||||
# CYRILLIC SMALL LETTER YAT: CYRILLIC SMALL LETTER TALL YAT
|
||||
0x0463: (0x1c87,), # 'ѣ': 'ᲇ'
|
||||
# CYRILLIC SMALL LETTER ROUNDED VE: CYRILLIC SMALL LETTER VE
|
||||
0x1c80: (0x0432,), # 'ᲀ': 'в'
|
||||
# CYRILLIC SMALL LETTER LONG-LEGGED DE: CYRILLIC SMALL LETTER DE
|
||||
0x1c81: (0x0434,), # 'ᲁ': 'д'
|
||||
# CYRILLIC SMALL LETTER NARROW O: CYRILLIC SMALL LETTER O
|
||||
0x1c82: (0x043e,), # 'ᲂ': 'о'
|
||||
# CYRILLIC SMALL LETTER WIDE ES: CYRILLIC SMALL LETTER ES
|
||||
0x1c83: (0x0441,), # 'ᲃ': 'с'
|
||||
# CYRILLIC SMALL LETTER TALL TE: CYRILLIC SMALL LETTER TE, CYRILLIC SMALL LETTER THREE-LEGGED TE
|
||||
0x1c84: (0x0442, 0x1c85), # 'ᲄ': 'тᲅ'
|
||||
# CYRILLIC SMALL LETTER THREE-LEGGED TE: CYRILLIC SMALL LETTER TE, CYRILLIC SMALL LETTER TALL TE
|
||||
0x1c85: (0x0442, 0x1c84), # 'ᲅ': 'тᲄ'
|
||||
# CYRILLIC SMALL LETTER TALL HARD SIGN: CYRILLIC SMALL LETTER HARD SIGN
|
||||
0x1c86: (0x044a,), # 'ᲆ': 'ъ'
|
||||
# CYRILLIC SMALL LETTER TALL YAT: CYRILLIC SMALL LETTER YAT
|
||||
0x1c87: (0x0463,), # 'ᲇ': 'ѣ'
|
||||
# CYRILLIC SMALL LETTER UNBLENDED UK: CYRILLIC SMALL LETTER MONOGRAPH UK
|
||||
0x1c88: (0xa64b,), # 'ᲈ': 'ꙋ'
|
||||
# LATIN SMALL LETTER S WITH DOT ABOVE: LATIN SMALL LETTER LONG S WITH DOT ABOVE
|
||||
0x1e61: (0x1e9b,), # 'ṡ': 'ẛ'
|
||||
# LATIN SMALL LETTER LONG S WITH DOT ABOVE: LATIN SMALL LETTER S WITH DOT ABOVE
|
||||
0x1e9b: (0x1e61,), # 'ẛ': 'ṡ'
|
||||
# GREEK PROSGEGRAMMENI: COMBINING GREEK YPOGEGRAMMENI, GREEK SMALL LETTER IOTA
|
||||
0x1fbe: (0x0345, 0x03b9), # 'ι': '\u0345ι'
|
||||
# GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
|
||||
0x1fd3: (0x0390,), # 'ΐ': 'ΐ'
|
||||
# GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
|
||||
0x1fe3: (0x03b0,), # 'ΰ': 'ΰ'
|
||||
# CYRILLIC SMALL LETTER MONOGRAPH UK: CYRILLIC SMALL LETTER UNBLENDED UK
|
||||
0xa64b: (0x1c88,), # 'ꙋ': 'ᲈ'
|
||||
# LATIN SMALL LIGATURE LONG S T: LATIN SMALL LIGATURE ST
|
||||
0xfb05: (0xfb06,), # 'ſt': 'st'
|
||||
# LATIN SMALL LIGATURE ST: LATIN SMALL LIGATURE LONG S T
|
||||
0xfb06: (0xfb05,), # 'st': 'ſt'
|
||||
}
|
||||
Vendored
+766
@@ -0,0 +1,766 @@
|
||||
#
|
||||
# Secret Labs' Regular Expression Engine
|
||||
#
|
||||
# convert template to internal format
|
||||
#
|
||||
# Copyright (c) 1997-2001 by Secret Labs AB. All rights reserved.
|
||||
#
|
||||
# See the __init__.py file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
"""Internal support module for sre"""
|
||||
|
||||
import _sre
|
||||
from . import _parser
|
||||
from ._constants import *
|
||||
from ._casefix import _EXTRA_CASES
|
||||
|
||||
assert _sre.MAGIC == MAGIC, "SRE module mismatch"
|
||||
|
||||
_LITERAL_CODES = {LITERAL, NOT_LITERAL}
|
||||
_SUCCESS_CODES = {SUCCESS, FAILURE}
|
||||
_ASSERT_CODES = {ASSERT, ASSERT_NOT}
|
||||
_UNIT_CODES = _LITERAL_CODES | {ANY, IN}
|
||||
|
||||
_REPEATING_CODES = {
|
||||
MIN_REPEAT: (REPEAT, MIN_UNTIL, MIN_REPEAT_ONE),
|
||||
MAX_REPEAT: (REPEAT, MAX_UNTIL, REPEAT_ONE),
|
||||
POSSESSIVE_REPEAT: (POSSESSIVE_REPEAT, SUCCESS, POSSESSIVE_REPEAT_ONE),
|
||||
}
|
||||
|
||||
def _combine_flags(flags, add_flags, del_flags,
|
||||
TYPE_FLAGS=_parser.TYPE_FLAGS):
|
||||
if add_flags & TYPE_FLAGS:
|
||||
flags &= ~TYPE_FLAGS
|
||||
return (flags | add_flags) & ~del_flags
|
||||
|
||||
def _compile(code, pattern, flags):
|
||||
# internal: compile a (sub)pattern
|
||||
emit = code.append
|
||||
_len = len
|
||||
LITERAL_CODES = _LITERAL_CODES
|
||||
REPEATING_CODES = _REPEATING_CODES
|
||||
SUCCESS_CODES = _SUCCESS_CODES
|
||||
ASSERT_CODES = _ASSERT_CODES
|
||||
iscased = None
|
||||
tolower = None
|
||||
fixes = None
|
||||
if flags & SRE_FLAG_IGNORECASE and not flags & SRE_FLAG_LOCALE:
|
||||
if flags & SRE_FLAG_UNICODE:
|
||||
iscased = _sre.unicode_iscased
|
||||
tolower = _sre.unicode_tolower
|
||||
fixes = _EXTRA_CASES
|
||||
else:
|
||||
iscased = _sre.ascii_iscased
|
||||
tolower = _sre.ascii_tolower
|
||||
for op, av in pattern:
|
||||
if op in LITERAL_CODES:
|
||||
if not flags & SRE_FLAG_IGNORECASE:
|
||||
emit(op)
|
||||
emit(av)
|
||||
elif flags & SRE_FLAG_LOCALE:
|
||||
emit(OP_LOCALE_IGNORE[op])
|
||||
emit(av)
|
||||
elif not iscased(av):
|
||||
emit(op)
|
||||
emit(av)
|
||||
else:
|
||||
lo = tolower(av)
|
||||
if not fixes: # ascii
|
||||
emit(OP_IGNORE[op])
|
||||
emit(lo)
|
||||
elif lo not in fixes:
|
||||
emit(OP_UNICODE_IGNORE[op])
|
||||
emit(lo)
|
||||
else:
|
||||
emit(IN_UNI_IGNORE)
|
||||
skip = _len(code); emit(0)
|
||||
if op is NOT_LITERAL:
|
||||
emit(NEGATE)
|
||||
for k in (lo,) + fixes[lo]:
|
||||
emit(LITERAL)
|
||||
emit(k)
|
||||
emit(FAILURE)
|
||||
code[skip] = _len(code) - skip
|
||||
elif op is IN:
|
||||
charset, hascased = _optimize_charset(av, iscased, tolower, fixes)
|
||||
if flags & SRE_FLAG_IGNORECASE and flags & SRE_FLAG_LOCALE:
|
||||
emit(IN_LOC_IGNORE)
|
||||
elif not hascased:
|
||||
emit(IN)
|
||||
elif not fixes: # ascii
|
||||
emit(IN_IGNORE)
|
||||
else:
|
||||
emit(IN_UNI_IGNORE)
|
||||
skip = _len(code); emit(0)
|
||||
_compile_charset(charset, flags, code)
|
||||
code[skip] = _len(code) - skip
|
||||
elif op is ANY:
|
||||
if flags & SRE_FLAG_DOTALL:
|
||||
emit(ANY_ALL)
|
||||
else:
|
||||
emit(ANY)
|
||||
elif op in REPEATING_CODES:
|
||||
if flags & SRE_FLAG_TEMPLATE:
|
||||
raise error("internal: unsupported template operator %r" % (op,))
|
||||
if _simple(av[2]):
|
||||
emit(REPEATING_CODES[op][2])
|
||||
skip = _len(code); emit(0)
|
||||
emit(av[0])
|
||||
emit(av[1])
|
||||
_compile(code, av[2], flags)
|
||||
emit(SUCCESS)
|
||||
code[skip] = _len(code) - skip
|
||||
else:
|
||||
emit(REPEATING_CODES[op][0])
|
||||
skip = _len(code); emit(0)
|
||||
emit(av[0])
|
||||
emit(av[1])
|
||||
_compile(code, av[2], flags)
|
||||
code[skip] = _len(code) - skip
|
||||
emit(REPEATING_CODES[op][1])
|
||||
elif op is SUBPATTERN:
|
||||
group, add_flags, del_flags, p = av
|
||||
if group:
|
||||
emit(MARK)
|
||||
emit((group-1)*2)
|
||||
# _compile_info(code, p, _combine_flags(flags, add_flags, del_flags))
|
||||
_compile(code, p, _combine_flags(flags, add_flags, del_flags))
|
||||
if group:
|
||||
emit(MARK)
|
||||
emit((group-1)*2+1)
|
||||
elif op is ATOMIC_GROUP:
|
||||
# Atomic Groups are handled by starting with an Atomic
|
||||
# Group op code, then putting in the atomic group pattern
|
||||
# and finally a success op code to tell any repeat
|
||||
# operations within the Atomic Group to stop eating and
|
||||
# pop their stack if they reach it
|
||||
emit(ATOMIC_GROUP)
|
||||
skip = _len(code); emit(0)
|
||||
_compile(code, av, flags)
|
||||
emit(SUCCESS)
|
||||
code[skip] = _len(code) - skip
|
||||
elif op in SUCCESS_CODES:
|
||||
emit(op)
|
||||
elif op in ASSERT_CODES:
|
||||
emit(op)
|
||||
skip = _len(code); emit(0)
|
||||
if av[0] >= 0:
|
||||
emit(0) # look ahead
|
||||
else:
|
||||
lo, hi = av[1].getwidth()
|
||||
if lo > MAXCODE:
|
||||
raise error("looks too much behind")
|
||||
if lo != hi:
|
||||
raise error("look-behind requires fixed-width pattern")
|
||||
emit(lo) # look behind
|
||||
_compile(code, av[1], flags)
|
||||
emit(SUCCESS)
|
||||
code[skip] = _len(code) - skip
|
||||
elif op is AT:
|
||||
emit(op)
|
||||
if flags & SRE_FLAG_MULTILINE:
|
||||
av = AT_MULTILINE.get(av, av)
|
||||
if flags & SRE_FLAG_LOCALE:
|
||||
av = AT_LOCALE.get(av, av)
|
||||
elif flags & SRE_FLAG_UNICODE:
|
||||
av = AT_UNICODE.get(av, av)
|
||||
emit(av)
|
||||
elif op is BRANCH:
|
||||
emit(op)
|
||||
tail = []
|
||||
tailappend = tail.append
|
||||
for av in av[1]:
|
||||
skip = _len(code); emit(0)
|
||||
# _compile_info(code, av, flags)
|
||||
_compile(code, av, flags)
|
||||
emit(JUMP)
|
||||
tailappend(_len(code)); emit(0)
|
||||
code[skip] = _len(code) - skip
|
||||
emit(FAILURE) # end of branch
|
||||
for tail in tail:
|
||||
code[tail] = _len(code) - tail
|
||||
elif op is CATEGORY:
|
||||
emit(op)
|
||||
if flags & SRE_FLAG_LOCALE:
|
||||
av = CH_LOCALE[av]
|
||||
elif flags & SRE_FLAG_UNICODE:
|
||||
av = CH_UNICODE[av]
|
||||
emit(av)
|
||||
elif op is GROUPREF:
|
||||
if not flags & SRE_FLAG_IGNORECASE:
|
||||
emit(op)
|
||||
elif flags & SRE_FLAG_LOCALE:
|
||||
emit(GROUPREF_LOC_IGNORE)
|
||||
elif not fixes: # ascii
|
||||
emit(GROUPREF_IGNORE)
|
||||
else:
|
||||
emit(GROUPREF_UNI_IGNORE)
|
||||
emit(av-1)
|
||||
elif op is GROUPREF_EXISTS:
|
||||
emit(op)
|
||||
emit(av[0]-1)
|
||||
skipyes = _len(code); emit(0)
|
||||
_compile(code, av[1], flags)
|
||||
if av[2]:
|
||||
emit(JUMP)
|
||||
skipno = _len(code); emit(0)
|
||||
code[skipyes] = _len(code) - skipyes + 1
|
||||
_compile(code, av[2], flags)
|
||||
code[skipno] = _len(code) - skipno
|
||||
else:
|
||||
code[skipyes] = _len(code) - skipyes + 1
|
||||
else:
|
||||
raise error("internal: unsupported operand type %r" % (op,))
|
||||
|
||||
def _compile_charset(charset, flags, code):
|
||||
# compile charset subprogram
|
||||
emit = code.append
|
||||
for op, av in charset:
|
||||
emit(op)
|
||||
if op is NEGATE:
|
||||
pass
|
||||
elif op is LITERAL:
|
||||
emit(av)
|
||||
elif op is RANGE or op is RANGE_UNI_IGNORE:
|
||||
emit(av[0])
|
||||
emit(av[1])
|
||||
elif op is CHARSET:
|
||||
code.extend(av)
|
||||
elif op is BIGCHARSET:
|
||||
code.extend(av)
|
||||
elif op is CATEGORY:
|
||||
if flags & SRE_FLAG_LOCALE:
|
||||
emit(CH_LOCALE[av])
|
||||
elif flags & SRE_FLAG_UNICODE:
|
||||
emit(CH_UNICODE[av])
|
||||
else:
|
||||
emit(av)
|
||||
else:
|
||||
raise error("internal: unsupported set operator %r" % (op,))
|
||||
emit(FAILURE)
|
||||
|
||||
def _optimize_charset(charset, iscased=None, fixup=None, fixes=None):
|
||||
# internal: optimize character set
|
||||
out = []
|
||||
tail = []
|
||||
charmap = bytearray(256)
|
||||
hascased = False
|
||||
for op, av in charset:
|
||||
while True:
|
||||
try:
|
||||
if op is LITERAL:
|
||||
if fixup:
|
||||
lo = fixup(av)
|
||||
charmap[lo] = 1
|
||||
if fixes and lo in fixes:
|
||||
for k in fixes[lo]:
|
||||
charmap[k] = 1
|
||||
if not hascased and iscased(av):
|
||||
hascased = True
|
||||
else:
|
||||
charmap[av] = 1
|
||||
elif op is RANGE:
|
||||
r = range(av[0], av[1]+1)
|
||||
if fixup:
|
||||
if fixes:
|
||||
for i in map(fixup, r):
|
||||
charmap[i] = 1
|
||||
if i in fixes:
|
||||
for k in fixes[i]:
|
||||
charmap[k] = 1
|
||||
else:
|
||||
for i in map(fixup, r):
|
||||
charmap[i] = 1
|
||||
if not hascased:
|
||||
hascased = any(map(iscased, r))
|
||||
else:
|
||||
for i in r:
|
||||
charmap[i] = 1
|
||||
elif op is NEGATE:
|
||||
out.append((op, av))
|
||||
else:
|
||||
tail.append((op, av))
|
||||
except IndexError:
|
||||
if len(charmap) == 256:
|
||||
# character set contains non-UCS1 character codes
|
||||
charmap += b'\0' * 0xff00
|
||||
continue
|
||||
# Character set contains non-BMP character codes.
|
||||
# For range, all BMP characters in the range are already
|
||||
# proceeded.
|
||||
if fixup:
|
||||
hascased = True
|
||||
# For now, IN_UNI_IGNORE+LITERAL and
|
||||
# IN_UNI_IGNORE+RANGE_UNI_IGNORE work for all non-BMP
|
||||
# characters, because two characters (at least one of
|
||||
# which is not in the BMP) match case-insensitively
|
||||
# if and only if:
|
||||
# 1) c1.lower() == c2.lower()
|
||||
# 2) c1.lower() == c2 or c1.lower().upper() == c2
|
||||
# Also, both c.lower() and c.lower().upper() are single
|
||||
# characters for every non-BMP character.
|
||||
if op is RANGE:
|
||||
op = RANGE_UNI_IGNORE
|
||||
tail.append((op, av))
|
||||
break
|
||||
|
||||
# compress character map
|
||||
runs = []
|
||||
q = 0
|
||||
while True:
|
||||
p = charmap.find(1, q)
|
||||
if p < 0:
|
||||
break
|
||||
if len(runs) >= 2:
|
||||
runs = None
|
||||
break
|
||||
q = charmap.find(0, p)
|
||||
if q < 0:
|
||||
runs.append((p, len(charmap)))
|
||||
break
|
||||
runs.append((p, q))
|
||||
if runs is not None:
|
||||
# use literal/range
|
||||
for p, q in runs:
|
||||
if q - p == 1:
|
||||
out.append((LITERAL, p))
|
||||
else:
|
||||
out.append((RANGE, (p, q - 1)))
|
||||
out += tail
|
||||
# if the case was changed or new representation is more compact
|
||||
if hascased or len(out) < len(charset):
|
||||
return out, hascased
|
||||
# else original character set is good enough
|
||||
return charset, hascased
|
||||
|
||||
# use bitmap
|
||||
if len(charmap) == 256:
|
||||
data = _mk_bitmap(charmap)
|
||||
out.append((CHARSET, data))
|
||||
out += tail
|
||||
return out, hascased
|
||||
|
||||
# To represent a big charset, first a bitmap of all characters in the
|
||||
# set is constructed. Then, this bitmap is sliced into chunks of 256
|
||||
# characters, duplicate chunks are eliminated, and each chunk is
|
||||
# given a number. In the compiled expression, the charset is
|
||||
# represented by a 32-bit word sequence, consisting of one word for
|
||||
# the number of different chunks, a sequence of 256 bytes (64 words)
|
||||
# of chunk numbers indexed by their original chunk position, and a
|
||||
# sequence of 256-bit chunks (8 words each).
|
||||
|
||||
# Compression is normally good: in a typical charset, large ranges of
|
||||
# Unicode will be either completely excluded (e.g. if only cyrillic
|
||||
# letters are to be matched), or completely included (e.g. if large
|
||||
# subranges of Kanji match). These ranges will be represented by
|
||||
# chunks of all one-bits or all zero-bits.
|
||||
|
||||
# Matching can be also done efficiently: the more significant byte of
|
||||
# the Unicode character is an index into the chunk number, and the
|
||||
# less significant byte is a bit index in the chunk (just like the
|
||||
# CHARSET matching).
|
||||
|
||||
charmap = bytes(charmap) # should be hashable
|
||||
comps = {}
|
||||
mapping = bytearray(256)
|
||||
block = 0
|
||||
data = bytearray()
|
||||
for i in range(0, 65536, 256):
|
||||
chunk = charmap[i: i + 256]
|
||||
if chunk in comps:
|
||||
mapping[i // 256] = comps[chunk]
|
||||
else:
|
||||
mapping[i // 256] = comps[chunk] = block
|
||||
block += 1
|
||||
data += chunk
|
||||
data = _mk_bitmap(data)
|
||||
data[0:0] = [block] + _bytes_to_codes(mapping)
|
||||
out.append((BIGCHARSET, data))
|
||||
out += tail
|
||||
return out, hascased
|
||||
|
||||
_CODEBITS = _sre.CODESIZE * 8
|
||||
MAXCODE = (1 << _CODEBITS) - 1
|
||||
_BITS_TRANS = b'0' + b'1' * 255
|
||||
def _mk_bitmap(bits, _CODEBITS=_CODEBITS, _int=int):
|
||||
s = bits.translate(_BITS_TRANS)[::-1]
|
||||
return [_int(s[i - _CODEBITS: i], 2)
|
||||
for i in range(len(s), 0, -_CODEBITS)]
|
||||
|
||||
def _bytes_to_codes(b):
|
||||
# Convert block indices to word array
|
||||
a = memoryview(b).cast('I')
|
||||
assert a.itemsize == _sre.CODESIZE
|
||||
assert len(a) * a.itemsize == len(b)
|
||||
return a.tolist()
|
||||
|
||||
def _simple(p):
|
||||
# check if this subpattern is a "simple" operator
|
||||
if len(p) != 1:
|
||||
return False
|
||||
op, av = p[0]
|
||||
if op is SUBPATTERN:
|
||||
return av[0] is None and _simple(av[-1])
|
||||
return op in _UNIT_CODES
|
||||
|
||||
def _generate_overlap_table(prefix):
|
||||
"""
|
||||
Generate an overlap table for the following prefix.
|
||||
An overlap table is a table of the same size as the prefix which
|
||||
informs about the potential self-overlap for each index in the prefix:
|
||||
- if overlap[i] == 0, prefix[i:] can't overlap prefix[0:...]
|
||||
- if overlap[i] == k with 0 < k <= i, prefix[i-k+1:i+1] overlaps with
|
||||
prefix[0:k]
|
||||
"""
|
||||
table = [0] * len(prefix)
|
||||
for i in range(1, len(prefix)):
|
||||
idx = table[i - 1]
|
||||
while prefix[i] != prefix[idx]:
|
||||
if idx == 0:
|
||||
table[i] = 0
|
||||
break
|
||||
idx = table[idx - 1]
|
||||
else:
|
||||
table[i] = idx + 1
|
||||
return table
|
||||
|
||||
def _get_iscased(flags):
|
||||
if not flags & SRE_FLAG_IGNORECASE:
|
||||
return None
|
||||
elif flags & SRE_FLAG_UNICODE:
|
||||
return _sre.unicode_iscased
|
||||
else:
|
||||
return _sre.ascii_iscased
|
||||
|
||||
def _get_literal_prefix(pattern, flags):
|
||||
# look for literal prefix
|
||||
prefix = []
|
||||
prefixappend = prefix.append
|
||||
prefix_skip = None
|
||||
iscased = _get_iscased(flags)
|
||||
for op, av in pattern.data:
|
||||
if op is LITERAL:
|
||||
if iscased and iscased(av):
|
||||
break
|
||||
prefixappend(av)
|
||||
elif op is SUBPATTERN:
|
||||
group, add_flags, del_flags, p = av
|
||||
flags1 = _combine_flags(flags, add_flags, del_flags)
|
||||
if flags1 & SRE_FLAG_IGNORECASE and flags1 & SRE_FLAG_LOCALE:
|
||||
break
|
||||
prefix1, prefix_skip1, got_all = _get_literal_prefix(p, flags1)
|
||||
if prefix_skip is None:
|
||||
if group is not None:
|
||||
prefix_skip = len(prefix)
|
||||
elif prefix_skip1 is not None:
|
||||
prefix_skip = len(prefix) + prefix_skip1
|
||||
prefix.extend(prefix1)
|
||||
if not got_all:
|
||||
break
|
||||
else:
|
||||
break
|
||||
else:
|
||||
return prefix, prefix_skip, True
|
||||
return prefix, prefix_skip, False
|
||||
|
||||
def _get_charset_prefix(pattern, flags):
|
||||
while True:
|
||||
if not pattern.data:
|
||||
return None
|
||||
op, av = pattern.data[0]
|
||||
if op is not SUBPATTERN:
|
||||
break
|
||||
group, add_flags, del_flags, pattern = av
|
||||
flags = _combine_flags(flags, add_flags, del_flags)
|
||||
if flags & SRE_FLAG_IGNORECASE and flags & SRE_FLAG_LOCALE:
|
||||
return None
|
||||
|
||||
iscased = _get_iscased(flags)
|
||||
if op is LITERAL:
|
||||
if iscased and iscased(av):
|
||||
return None
|
||||
return [(op, av)]
|
||||
elif op is BRANCH:
|
||||
charset = []
|
||||
charsetappend = charset.append
|
||||
for p in av[1]:
|
||||
if not p:
|
||||
return None
|
||||
op, av = p[0]
|
||||
if op is LITERAL and not (iscased and iscased(av)):
|
||||
charsetappend((op, av))
|
||||
else:
|
||||
return None
|
||||
return charset
|
||||
elif op is IN:
|
||||
charset = av
|
||||
if iscased:
|
||||
for op, av in charset:
|
||||
if op is LITERAL:
|
||||
if iscased(av):
|
||||
return None
|
||||
elif op is RANGE:
|
||||
if av[1] > 0xffff:
|
||||
return None
|
||||
if any(map(iscased, range(av[0], av[1]+1))):
|
||||
return None
|
||||
return charset
|
||||
return None
|
||||
|
||||
def _compile_info(code, pattern, flags):
|
||||
# internal: compile an info block. in the current version,
|
||||
# this contains min/max pattern width, and an optional literal
|
||||
# prefix or a character map
|
||||
lo, hi = pattern.getwidth()
|
||||
if hi > MAXCODE:
|
||||
hi = MAXCODE
|
||||
if lo == 0:
|
||||
code.extend([INFO, 4, 0, lo, hi])
|
||||
return
|
||||
# look for a literal prefix
|
||||
prefix = []
|
||||
prefix_skip = 0
|
||||
charset = [] # not used
|
||||
if not (flags & SRE_FLAG_IGNORECASE and flags & SRE_FLAG_LOCALE):
|
||||
# look for literal prefix
|
||||
prefix, prefix_skip, got_all = _get_literal_prefix(pattern, flags)
|
||||
# if no prefix, look for charset prefix
|
||||
if not prefix:
|
||||
charset = _get_charset_prefix(pattern, flags)
|
||||
## if prefix:
|
||||
## print("*** PREFIX", prefix, prefix_skip)
|
||||
## if charset:
|
||||
## print("*** CHARSET", charset)
|
||||
# add an info block
|
||||
emit = code.append
|
||||
emit(INFO)
|
||||
skip = len(code); emit(0)
|
||||
# literal flag
|
||||
mask = 0
|
||||
if prefix:
|
||||
mask = SRE_INFO_PREFIX
|
||||
if prefix_skip is None and got_all:
|
||||
mask = mask | SRE_INFO_LITERAL
|
||||
elif charset:
|
||||
mask = mask | SRE_INFO_CHARSET
|
||||
emit(mask)
|
||||
# pattern length
|
||||
if lo < MAXCODE:
|
||||
emit(lo)
|
||||
else:
|
||||
emit(MAXCODE)
|
||||
prefix = prefix[:MAXCODE]
|
||||
emit(hi)
|
||||
# add literal prefix
|
||||
if prefix:
|
||||
emit(len(prefix)) # length
|
||||
if prefix_skip is None:
|
||||
prefix_skip = len(prefix)
|
||||
emit(prefix_skip) # skip
|
||||
code.extend(prefix)
|
||||
# generate overlap table
|
||||
code.extend(_generate_overlap_table(prefix))
|
||||
elif charset:
|
||||
charset, hascased = _optimize_charset(charset)
|
||||
assert not hascased
|
||||
_compile_charset(charset, flags, code)
|
||||
code[skip] = len(code) - skip
|
||||
|
||||
def isstring(obj):
|
||||
return isinstance(obj, (str, bytes))
|
||||
|
||||
def _code(p, flags):
|
||||
|
||||
flags = p.state.flags | flags
|
||||
code = []
|
||||
|
||||
# compile info block
|
||||
_compile_info(code, p, flags)
|
||||
|
||||
# compile the pattern
|
||||
_compile(code, p.data, flags)
|
||||
|
||||
code.append(SUCCESS)
|
||||
|
||||
return code
|
||||
|
||||
def _hex_code(code):
|
||||
return '[%s]' % ', '.join('%#0*x' % (_sre.CODESIZE*2+2, x) for x in code)
|
||||
|
||||
def dis(code):
|
||||
import sys
|
||||
|
||||
labels = set()
|
||||
level = 0
|
||||
offset_width = len(str(len(code) - 1))
|
||||
|
||||
def dis_(start, end):
|
||||
def print_(*args, to=None):
|
||||
if to is not None:
|
||||
labels.add(to)
|
||||
args += ('(to %d)' % (to,),)
|
||||
print('%*d%s ' % (offset_width, start, ':' if start in labels else '.'),
|
||||
end=' '*(level-1))
|
||||
print(*args)
|
||||
|
||||
def print_2(*args):
|
||||
print(end=' '*(offset_width + 2*level))
|
||||
print(*args)
|
||||
|
||||
nonlocal level
|
||||
level += 1
|
||||
i = start
|
||||
while i < end:
|
||||
start = i
|
||||
op = code[i]
|
||||
i += 1
|
||||
op = OPCODES[op]
|
||||
if op in (SUCCESS, FAILURE, ANY, ANY_ALL,
|
||||
MAX_UNTIL, MIN_UNTIL, NEGATE):
|
||||
print_(op)
|
||||
elif op in (LITERAL, NOT_LITERAL,
|
||||
LITERAL_IGNORE, NOT_LITERAL_IGNORE,
|
||||
LITERAL_UNI_IGNORE, NOT_LITERAL_UNI_IGNORE,
|
||||
LITERAL_LOC_IGNORE, NOT_LITERAL_LOC_IGNORE):
|
||||
arg = code[i]
|
||||
i += 1
|
||||
print_(op, '%#02x (%r)' % (arg, chr(arg)))
|
||||
elif op is AT:
|
||||
arg = code[i]
|
||||
i += 1
|
||||
arg = str(ATCODES[arg])
|
||||
assert arg[:3] == 'AT_'
|
||||
print_(op, arg[3:])
|
||||
elif op is CATEGORY:
|
||||
arg = code[i]
|
||||
i += 1
|
||||
arg = str(CHCODES[arg])
|
||||
assert arg[:9] == 'CATEGORY_'
|
||||
print_(op, arg[9:])
|
||||
elif op in (IN, IN_IGNORE, IN_UNI_IGNORE, IN_LOC_IGNORE):
|
||||
skip = code[i]
|
||||
print_(op, skip, to=i+skip)
|
||||
dis_(i+1, i+skip)
|
||||
i += skip
|
||||
elif op in (RANGE, RANGE_UNI_IGNORE):
|
||||
lo, hi = code[i: i+2]
|
||||
i += 2
|
||||
print_(op, '%#02x %#02x (%r-%r)' % (lo, hi, chr(lo), chr(hi)))
|
||||
elif op is CHARSET:
|
||||
print_(op, _hex_code(code[i: i + 256//_CODEBITS]))
|
||||
i += 256//_CODEBITS
|
||||
elif op is BIGCHARSET:
|
||||
arg = code[i]
|
||||
i += 1
|
||||
mapping = list(b''.join(x.to_bytes(_sre.CODESIZE, sys.byteorder)
|
||||
for x in code[i: i + 256//_sre.CODESIZE]))
|
||||
print_(op, arg, mapping)
|
||||
i += 256//_sre.CODESIZE
|
||||
level += 1
|
||||
for j in range(arg):
|
||||
print_2(_hex_code(code[i: i + 256//_CODEBITS]))
|
||||
i += 256//_CODEBITS
|
||||
level -= 1
|
||||
elif op in (MARK, GROUPREF, GROUPREF_IGNORE, GROUPREF_UNI_IGNORE,
|
||||
GROUPREF_LOC_IGNORE):
|
||||
arg = code[i]
|
||||
i += 1
|
||||
print_(op, arg)
|
||||
elif op is JUMP:
|
||||
skip = code[i]
|
||||
print_(op, skip, to=i+skip)
|
||||
i += 1
|
||||
elif op is BRANCH:
|
||||
skip = code[i]
|
||||
print_(op, skip, to=i+skip)
|
||||
while skip:
|
||||
dis_(i+1, i+skip)
|
||||
i += skip
|
||||
start = i
|
||||
skip = code[i]
|
||||
if skip:
|
||||
print_('branch', skip, to=i+skip)
|
||||
else:
|
||||
print_(FAILURE)
|
||||
i += 1
|
||||
elif op in (REPEAT, REPEAT_ONE, MIN_REPEAT_ONE,
|
||||
POSSESSIVE_REPEAT, POSSESSIVE_REPEAT_ONE):
|
||||
skip, min, max = code[i: i+3]
|
||||
if max == MAXREPEAT:
|
||||
max = 'MAXREPEAT'
|
||||
print_(op, skip, min, max, to=i+skip)
|
||||
dis_(i+3, i+skip)
|
||||
i += skip
|
||||
elif op is GROUPREF_EXISTS:
|
||||
arg, skip = code[i: i+2]
|
||||
print_(op, arg, skip, to=i+skip)
|
||||
i += 2
|
||||
elif op in (ASSERT, ASSERT_NOT):
|
||||
skip, arg = code[i: i+2]
|
||||
print_(op, skip, arg, to=i+skip)
|
||||
dis_(i+2, i+skip)
|
||||
i += skip
|
||||
elif op is ATOMIC_GROUP:
|
||||
skip = code[i]
|
||||
print_(op, skip, to=i+skip)
|
||||
dis_(i+1, i+skip)
|
||||
i += skip
|
||||
elif op is INFO:
|
||||
skip, flags, min, max = code[i: i+4]
|
||||
if max == MAXREPEAT:
|
||||
max = 'MAXREPEAT'
|
||||
print_(op, skip, bin(flags), min, max, to=i+skip)
|
||||
start = i+4
|
||||
if flags & SRE_INFO_PREFIX:
|
||||
prefix_len, prefix_skip = code[i+4: i+6]
|
||||
print_2(' prefix_skip', prefix_skip)
|
||||
start = i + 6
|
||||
prefix = code[start: start+prefix_len]
|
||||
print_2(' prefix',
|
||||
'[%s]' % ', '.join('%#02x' % x for x in prefix),
|
||||
'(%r)' % ''.join(map(chr, prefix)))
|
||||
start += prefix_len
|
||||
print_2(' overlap', code[start: start+prefix_len])
|
||||
start += prefix_len
|
||||
if flags & SRE_INFO_CHARSET:
|
||||
level += 1
|
||||
print_2('in')
|
||||
dis_(start, i+skip)
|
||||
level -= 1
|
||||
i += skip
|
||||
else:
|
||||
raise ValueError(op)
|
||||
|
||||
level -= 1
|
||||
|
||||
dis_(0, len(code))
|
||||
|
||||
|
||||
def compile(p, flags=0):
|
||||
# internal: convert pattern list to internal format
|
||||
|
||||
if isstring(p):
|
||||
pattern = p
|
||||
p = _parser.parse(p, flags)
|
||||
else:
|
||||
pattern = None
|
||||
|
||||
code = _code(p, flags)
|
||||
|
||||
if flags & SRE_FLAG_DEBUG:
|
||||
print()
|
||||
dis(code)
|
||||
|
||||
# map in either direction
|
||||
groupindex = p.state.groupdict
|
||||
indexgroup = [None] * p.state.groups
|
||||
for k, i in groupindex.items():
|
||||
indexgroup[i] = k
|
||||
|
||||
return _sre.compile(
|
||||
pattern, flags | p.state.flags, code,
|
||||
p.state.groups-1,
|
||||
groupindex, tuple(indexgroup)
|
||||
)
|
||||
|
||||
Vendored
+221
@@ -0,0 +1,221 @@
|
||||
#
|
||||
# Secret Labs' Regular Expression Engine
|
||||
#
|
||||
# various symbols used by the regular expression engine.
|
||||
# run this script to update the _sre include files!
|
||||
#
|
||||
# Copyright (c) 1998-2001 by Secret Labs AB. All rights reserved.
|
||||
#
|
||||
# See the __init__.py file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
"""Internal support module for sre"""
|
||||
|
||||
# update when constants are added or removed
|
||||
|
||||
MAGIC = 20221023
|
||||
|
||||
from _sre import MAXREPEAT, MAXGROUPS
|
||||
|
||||
# SRE standard exception (access as sre.error)
|
||||
# should this really be here?
|
||||
|
||||
class error(Exception):
|
||||
"""Exception raised for invalid regular expressions.
|
||||
|
||||
Attributes:
|
||||
|
||||
msg: The unformatted error message
|
||||
pattern: The regular expression pattern
|
||||
pos: The index in the pattern where compilation failed (may be None)
|
||||
lineno: The line corresponding to pos (may be None)
|
||||
colno: The column corresponding to pos (may be None)
|
||||
"""
|
||||
|
||||
__module__ = 're'
|
||||
|
||||
def __init__(self, msg, pattern=None, pos=None):
|
||||
self.msg = msg
|
||||
self.pattern = pattern
|
||||
self.pos = pos
|
||||
if pattern is not None and pos is not None:
|
||||
msg = '%s at position %d' % (msg, pos)
|
||||
if isinstance(pattern, str):
|
||||
newline = '\n'
|
||||
else:
|
||||
newline = b'\n'
|
||||
self.lineno = pattern.count(newline, 0, pos) + 1
|
||||
self.colno = pos - pattern.rfind(newline, 0, pos)
|
||||
if newline in pattern:
|
||||
msg = '%s (line %d, column %d)' % (msg, self.lineno, self.colno)
|
||||
else:
|
||||
self.lineno = self.colno = None
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class _NamedIntConstant(int):
|
||||
def __new__(cls, value, name):
|
||||
self = super(_NamedIntConstant, cls).__new__(cls, value)
|
||||
self.name = name
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
__reduce__ = None
|
||||
|
||||
MAXREPEAT = _NamedIntConstant(MAXREPEAT, 'MAXREPEAT')
|
||||
|
||||
def _makecodes(*names):
|
||||
items = [_NamedIntConstant(i, name) for i, name in enumerate(names)]
|
||||
globals().update({item.name: item for item in items})
|
||||
return items
|
||||
|
||||
# operators
|
||||
OPCODES = _makecodes(
|
||||
# failure=0 success=1 (just because it looks better that way :-)
|
||||
'FAILURE', 'SUCCESS',
|
||||
|
||||
'ANY', 'ANY_ALL',
|
||||
'ASSERT', 'ASSERT_NOT',
|
||||
'AT',
|
||||
'BRANCH',
|
||||
'CATEGORY',
|
||||
'CHARSET', 'BIGCHARSET',
|
||||
'GROUPREF', 'GROUPREF_EXISTS',
|
||||
'IN',
|
||||
'INFO',
|
||||
'JUMP',
|
||||
'LITERAL',
|
||||
'MARK',
|
||||
'MAX_UNTIL',
|
||||
'MIN_UNTIL',
|
||||
'NOT_LITERAL',
|
||||
'NEGATE',
|
||||
'RANGE',
|
||||
'REPEAT',
|
||||
'REPEAT_ONE',
|
||||
'SUBPATTERN',
|
||||
'MIN_REPEAT_ONE',
|
||||
'ATOMIC_GROUP',
|
||||
'POSSESSIVE_REPEAT',
|
||||
'POSSESSIVE_REPEAT_ONE',
|
||||
|
||||
'GROUPREF_IGNORE',
|
||||
'IN_IGNORE',
|
||||
'LITERAL_IGNORE',
|
||||
'NOT_LITERAL_IGNORE',
|
||||
|
||||
'GROUPREF_LOC_IGNORE',
|
||||
'IN_LOC_IGNORE',
|
||||
'LITERAL_LOC_IGNORE',
|
||||
'NOT_LITERAL_LOC_IGNORE',
|
||||
|
||||
'GROUPREF_UNI_IGNORE',
|
||||
'IN_UNI_IGNORE',
|
||||
'LITERAL_UNI_IGNORE',
|
||||
'NOT_LITERAL_UNI_IGNORE',
|
||||
'RANGE_UNI_IGNORE',
|
||||
|
||||
# The following opcodes are only occurred in the parser output,
|
||||
# but not in the compiled code.
|
||||
'MIN_REPEAT', 'MAX_REPEAT',
|
||||
)
|
||||
del OPCODES[-2:] # remove MIN_REPEAT and MAX_REPEAT
|
||||
|
||||
# positions
|
||||
ATCODES = _makecodes(
|
||||
'AT_BEGINNING', 'AT_BEGINNING_LINE', 'AT_BEGINNING_STRING',
|
||||
'AT_BOUNDARY', 'AT_NON_BOUNDARY',
|
||||
'AT_END', 'AT_END_LINE', 'AT_END_STRING',
|
||||
|
||||
'AT_LOC_BOUNDARY', 'AT_LOC_NON_BOUNDARY',
|
||||
|
||||
'AT_UNI_BOUNDARY', 'AT_UNI_NON_BOUNDARY',
|
||||
)
|
||||
|
||||
# categories
|
||||
CHCODES = _makecodes(
|
||||
'CATEGORY_DIGIT', 'CATEGORY_NOT_DIGIT',
|
||||
'CATEGORY_SPACE', 'CATEGORY_NOT_SPACE',
|
||||
'CATEGORY_WORD', 'CATEGORY_NOT_WORD',
|
||||
'CATEGORY_LINEBREAK', 'CATEGORY_NOT_LINEBREAK',
|
||||
|
||||
'CATEGORY_LOC_WORD', 'CATEGORY_LOC_NOT_WORD',
|
||||
|
||||
'CATEGORY_UNI_DIGIT', 'CATEGORY_UNI_NOT_DIGIT',
|
||||
'CATEGORY_UNI_SPACE', 'CATEGORY_UNI_NOT_SPACE',
|
||||
'CATEGORY_UNI_WORD', 'CATEGORY_UNI_NOT_WORD',
|
||||
'CATEGORY_UNI_LINEBREAK', 'CATEGORY_UNI_NOT_LINEBREAK',
|
||||
)
|
||||
|
||||
|
||||
# replacement operations for "ignore case" mode
|
||||
OP_IGNORE = {
|
||||
LITERAL: LITERAL_IGNORE,
|
||||
NOT_LITERAL: NOT_LITERAL_IGNORE,
|
||||
}
|
||||
|
||||
OP_LOCALE_IGNORE = {
|
||||
LITERAL: LITERAL_LOC_IGNORE,
|
||||
NOT_LITERAL: NOT_LITERAL_LOC_IGNORE,
|
||||
}
|
||||
|
||||
OP_UNICODE_IGNORE = {
|
||||
LITERAL: LITERAL_UNI_IGNORE,
|
||||
NOT_LITERAL: NOT_LITERAL_UNI_IGNORE,
|
||||
}
|
||||
|
||||
AT_MULTILINE = {
|
||||
AT_BEGINNING: AT_BEGINNING_LINE,
|
||||
AT_END: AT_END_LINE
|
||||
}
|
||||
|
||||
AT_LOCALE = {
|
||||
AT_BOUNDARY: AT_LOC_BOUNDARY,
|
||||
AT_NON_BOUNDARY: AT_LOC_NON_BOUNDARY
|
||||
}
|
||||
|
||||
AT_UNICODE = {
|
||||
AT_BOUNDARY: AT_UNI_BOUNDARY,
|
||||
AT_NON_BOUNDARY: AT_UNI_NON_BOUNDARY
|
||||
}
|
||||
|
||||
CH_LOCALE = {
|
||||
CATEGORY_DIGIT: CATEGORY_DIGIT,
|
||||
CATEGORY_NOT_DIGIT: CATEGORY_NOT_DIGIT,
|
||||
CATEGORY_SPACE: CATEGORY_SPACE,
|
||||
CATEGORY_NOT_SPACE: CATEGORY_NOT_SPACE,
|
||||
CATEGORY_WORD: CATEGORY_LOC_WORD,
|
||||
CATEGORY_NOT_WORD: CATEGORY_LOC_NOT_WORD,
|
||||
CATEGORY_LINEBREAK: CATEGORY_LINEBREAK,
|
||||
CATEGORY_NOT_LINEBREAK: CATEGORY_NOT_LINEBREAK
|
||||
}
|
||||
|
||||
CH_UNICODE = {
|
||||
CATEGORY_DIGIT: CATEGORY_UNI_DIGIT,
|
||||
CATEGORY_NOT_DIGIT: CATEGORY_UNI_NOT_DIGIT,
|
||||
CATEGORY_SPACE: CATEGORY_UNI_SPACE,
|
||||
CATEGORY_NOT_SPACE: CATEGORY_UNI_NOT_SPACE,
|
||||
CATEGORY_WORD: CATEGORY_UNI_WORD,
|
||||
CATEGORY_NOT_WORD: CATEGORY_UNI_NOT_WORD,
|
||||
CATEGORY_LINEBREAK: CATEGORY_UNI_LINEBREAK,
|
||||
CATEGORY_NOT_LINEBREAK: CATEGORY_UNI_NOT_LINEBREAK
|
||||
}
|
||||
|
||||
# flags
|
||||
SRE_FLAG_TEMPLATE = 1 # template mode (unknown purpose, deprecated)
|
||||
SRE_FLAG_IGNORECASE = 2 # case insensitive
|
||||
SRE_FLAG_LOCALE = 4 # honour system locale
|
||||
SRE_FLAG_MULTILINE = 8 # treat target as multiline string
|
||||
SRE_FLAG_DOTALL = 16 # treat target as a single string
|
||||
SRE_FLAG_UNICODE = 32 # use unicode "locale"
|
||||
SRE_FLAG_VERBOSE = 64 # ignore whitespace and comments
|
||||
SRE_FLAG_DEBUG = 128 # debugging
|
||||
SRE_FLAG_ASCII = 256 # use ascii "locale"
|
||||
|
||||
# flags for INFO primitive
|
||||
SRE_INFO_PREFIX = 1 # has prefix
|
||||
SRE_INFO_LITERAL = 2 # entire pattern is literal (given by prefix)
|
||||
SRE_INFO_CHARSET = 4 # pattern starts with character from given set
|
||||
RE_INFO_CHARSET = 4 # pattern starts with character from given set
|
||||
Vendored
+1080
File diff suppressed because it is too large.
Load diff
Vendored
+6
-783
@@ -1,784 +1,7 @@
|
||||
#
|
||||
# Secret Labs' Regular Expression Engine
|
||||
#
|
||||
# convert template to internal format
|
||||
#
|
||||
# Copyright (c) 1997-2001 by Secret Labs AB. All rights reserved.
|
||||
#
|
||||
# See the sre.py file for information on usage and redistribution.
|
||||
#
|
||||
import warnings
|
||||
warnings.warn(f"module {__name__!r} is deprecated",
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
|
||||
"""Internal support module for sre"""
|
||||
|
||||
import _sre
|
||||
import sre_parse
|
||||
from sre_constants import *
|
||||
|
||||
assert _sre.MAGIC == MAGIC, "SRE module mismatch"
|
||||
|
||||
_LITERAL_CODES = {LITERAL, NOT_LITERAL}
|
||||
_REPEATING_CODES = {REPEAT, MIN_REPEAT, MAX_REPEAT}
|
||||
_SUCCESS_CODES = {SUCCESS, FAILURE}
|
||||
_ASSERT_CODES = {ASSERT, ASSERT_NOT}
|
||||
_UNIT_CODES = _LITERAL_CODES | {ANY, IN}
|
||||
|
||||
# Sets of lowercase characters which have the same uppercase.
|
||||
_equivalences = (
|
||||
# LATIN SMALL LETTER I, LATIN SMALL LETTER DOTLESS I
|
||||
(0x69, 0x131), # iı
|
||||
# LATIN SMALL LETTER S, LATIN SMALL LETTER LONG S
|
||||
(0x73, 0x17f), # sſ
|
||||
# MICRO SIGN, GREEK SMALL LETTER MU
|
||||
(0xb5, 0x3bc), # µμ
|
||||
# COMBINING GREEK YPOGEGRAMMENI, GREEK SMALL LETTER IOTA, GREEK PROSGEGRAMMENI
|
||||
(0x345, 0x3b9, 0x1fbe), # \u0345ιι
|
||||
# GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS, GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
|
||||
(0x390, 0x1fd3), # ΐΐ
|
||||
# GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS, GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
|
||||
(0x3b0, 0x1fe3), # ΰΰ
|
||||
# GREEK SMALL LETTER BETA, GREEK BETA SYMBOL
|
||||
(0x3b2, 0x3d0), # βϐ
|
||||
# GREEK SMALL LETTER EPSILON, GREEK LUNATE EPSILON SYMBOL
|
||||
(0x3b5, 0x3f5), # εϵ
|
||||
# GREEK SMALL LETTER THETA, GREEK THETA SYMBOL
|
||||
(0x3b8, 0x3d1), # θϑ
|
||||
# GREEK SMALL LETTER KAPPA, GREEK KAPPA SYMBOL
|
||||
(0x3ba, 0x3f0), # κϰ
|
||||
# GREEK SMALL LETTER PI, GREEK PI SYMBOL
|
||||
(0x3c0, 0x3d6), # πϖ
|
||||
# GREEK SMALL LETTER RHO, GREEK RHO SYMBOL
|
||||
(0x3c1, 0x3f1), # ρϱ
|
||||
# GREEK SMALL LETTER FINAL SIGMA, GREEK SMALL LETTER SIGMA
|
||||
(0x3c2, 0x3c3), # ςσ
|
||||
# GREEK SMALL LETTER PHI, GREEK PHI SYMBOL
|
||||
(0x3c6, 0x3d5), # φϕ
|
||||
# LATIN SMALL LETTER S WITH DOT ABOVE, LATIN SMALL LETTER LONG S WITH DOT ABOVE
|
||||
(0x1e61, 0x1e9b), # ṡẛ
|
||||
# LATIN SMALL LIGATURE LONG S T, LATIN SMALL LIGATURE ST
|
||||
(0xfb05, 0xfb06), # ſtst
|
||||
)
|
||||
|
||||
# Maps the lowercase code to lowercase codes which have the same uppercase.
|
||||
_ignorecase_fixes = {i: tuple(j for j in t if i != j)
|
||||
for t in _equivalences for i in t}
|
||||
|
||||
def _combine_flags(flags, add_flags, del_flags,
|
||||
TYPE_FLAGS=sre_parse.TYPE_FLAGS):
|
||||
if add_flags & TYPE_FLAGS:
|
||||
flags &= ~TYPE_FLAGS
|
||||
return (flags | add_flags) & ~del_flags
|
||||
|
||||
def _compile(code, pattern, flags):
|
||||
# internal: compile a (sub)pattern
|
||||
emit = code.append
|
||||
_len = len
|
||||
LITERAL_CODES = _LITERAL_CODES
|
||||
REPEATING_CODES = _REPEATING_CODES
|
||||
SUCCESS_CODES = _SUCCESS_CODES
|
||||
ASSERT_CODES = _ASSERT_CODES
|
||||
iscased = None
|
||||
tolower = None
|
||||
fixes = None
|
||||
if flags & SRE_FLAG_IGNORECASE and not flags & SRE_FLAG_LOCALE:
|
||||
if flags & SRE_FLAG_UNICODE:
|
||||
iscased = _sre.unicode_iscased
|
||||
tolower = _sre.unicode_tolower
|
||||
fixes = _ignorecase_fixes
|
||||
else:
|
||||
iscased = _sre.ascii_iscased
|
||||
tolower = _sre.ascii_tolower
|
||||
for op, av in pattern:
|
||||
if op in LITERAL_CODES:
|
||||
if not flags & SRE_FLAG_IGNORECASE:
|
||||
emit(op)
|
||||
emit(av)
|
||||
elif flags & SRE_FLAG_LOCALE:
|
||||
emit(OP_LOCALE_IGNORE[op])
|
||||
emit(av)
|
||||
elif not iscased(av):
|
||||
emit(op)
|
||||
emit(av)
|
||||
else:
|
||||
lo = tolower(av)
|
||||
if not fixes: # ascii
|
||||
emit(OP_IGNORE[op])
|
||||
emit(lo)
|
||||
elif lo not in fixes:
|
||||
emit(OP_UNICODE_IGNORE[op])
|
||||
emit(lo)
|
||||
else:
|
||||
emit(IN_UNI_IGNORE)
|
||||
skip = _len(code); emit(0)
|
||||
if op is NOT_LITERAL:
|
||||
emit(NEGATE)
|
||||
for k in (lo,) + fixes[lo]:
|
||||
emit(LITERAL)
|
||||
emit(k)
|
||||
emit(FAILURE)
|
||||
code[skip] = _len(code) - skip
|
||||
elif op is IN:
|
||||
charset, hascased = _optimize_charset(av, iscased, tolower, fixes)
|
||||
if flags & SRE_FLAG_IGNORECASE and flags & SRE_FLAG_LOCALE:
|
||||
emit(IN_LOC_IGNORE)
|
||||
elif not hascased:
|
||||
emit(IN)
|
||||
elif not fixes: # ascii
|
||||
emit(IN_IGNORE)
|
||||
else:
|
||||
emit(IN_UNI_IGNORE)
|
||||
skip = _len(code); emit(0)
|
||||
_compile_charset(charset, flags, code)
|
||||
code[skip] = _len(code) - skip
|
||||
elif op is ANY:
|
||||
if flags & SRE_FLAG_DOTALL:
|
||||
emit(ANY_ALL)
|
||||
else:
|
||||
emit(ANY)
|
||||
elif op in REPEATING_CODES:
|
||||
if flags & SRE_FLAG_TEMPLATE:
|
||||
raise error("internal: unsupported template operator %r" % (op,))
|
||||
if _simple(av[2]):
|
||||
if op is MAX_REPEAT:
|
||||
emit(REPEAT_ONE)
|
||||
else:
|
||||
emit(MIN_REPEAT_ONE)
|
||||
skip = _len(code); emit(0)
|
||||
emit(av[0])
|
||||
emit(av[1])
|
||||
_compile(code, av[2], flags)
|
||||
emit(SUCCESS)
|
||||
code[skip] = _len(code) - skip
|
||||
else:
|
||||
emit(REPEAT)
|
||||
skip = _len(code); emit(0)
|
||||
emit(av[0])
|
||||
emit(av[1])
|
||||
_compile(code, av[2], flags)
|
||||
code[skip] = _len(code) - skip
|
||||
if op is MAX_REPEAT:
|
||||
emit(MAX_UNTIL)
|
||||
else:
|
||||
emit(MIN_UNTIL)
|
||||
elif op is SUBPATTERN:
|
||||
group, add_flags, del_flags, p = av
|
||||
if group:
|
||||
emit(MARK)
|
||||
emit((group-1)*2)
|
||||
# _compile_info(code, p, _combine_flags(flags, add_flags, del_flags))
|
||||
_compile(code, p, _combine_flags(flags, add_flags, del_flags))
|
||||
if group:
|
||||
emit(MARK)
|
||||
emit((group-1)*2+1)
|
||||
elif op in SUCCESS_CODES:
|
||||
emit(op)
|
||||
elif op in ASSERT_CODES:
|
||||
emit(op)
|
||||
skip = _len(code); emit(0)
|
||||
if av[0] >= 0:
|
||||
emit(0) # look ahead
|
||||
else:
|
||||
lo, hi = av[1].getwidth()
|
||||
if lo != hi:
|
||||
raise error("look-behind requires fixed-width pattern")
|
||||
emit(lo) # look behind
|
||||
_compile(code, av[1], flags)
|
||||
emit(SUCCESS)
|
||||
code[skip] = _len(code) - skip
|
||||
elif op is CALL:
|
||||
emit(op)
|
||||
skip = _len(code); emit(0)
|
||||
_compile(code, av, flags)
|
||||
emit(SUCCESS)
|
||||
code[skip] = _len(code) - skip
|
||||
elif op is AT:
|
||||
emit(op)
|
||||
if flags & SRE_FLAG_MULTILINE:
|
||||
av = AT_MULTILINE.get(av, av)
|
||||
if flags & SRE_FLAG_LOCALE:
|
||||
av = AT_LOCALE.get(av, av)
|
||||
elif flags & SRE_FLAG_UNICODE:
|
||||
av = AT_UNICODE.get(av, av)
|
||||
emit(av)
|
||||
elif op is BRANCH:
|
||||
emit(op)
|
||||
tail = []
|
||||
tailappend = tail.append
|
||||
for av in av[1]:
|
||||
skip = _len(code); emit(0)
|
||||
# _compile_info(code, av, flags)
|
||||
_compile(code, av, flags)
|
||||
emit(JUMP)
|
||||
tailappend(_len(code)); emit(0)
|
||||
code[skip] = _len(code) - skip
|
||||
emit(FAILURE) # end of branch
|
||||
for tail in tail:
|
||||
code[tail] = _len(code) - tail
|
||||
elif op is CATEGORY:
|
||||
emit(op)
|
||||
if flags & SRE_FLAG_LOCALE:
|
||||
av = CH_LOCALE[av]
|
||||
elif flags & SRE_FLAG_UNICODE:
|
||||
av = CH_UNICODE[av]
|
||||
emit(av)
|
||||
elif op is GROUPREF:
|
||||
if not flags & SRE_FLAG_IGNORECASE:
|
||||
emit(op)
|
||||
elif flags & SRE_FLAG_LOCALE:
|
||||
emit(GROUPREF_LOC_IGNORE)
|
||||
elif not fixes: # ascii
|
||||
emit(GROUPREF_IGNORE)
|
||||
else:
|
||||
emit(GROUPREF_UNI_IGNORE)
|
||||
emit(av-1)
|
||||
elif op is GROUPREF_EXISTS:
|
||||
emit(op)
|
||||
emit(av[0]-1)
|
||||
skipyes = _len(code); emit(0)
|
||||
_compile(code, av[1], flags)
|
||||
if av[2]:
|
||||
emit(JUMP)
|
||||
skipno = _len(code); emit(0)
|
||||
code[skipyes] = _len(code) - skipyes + 1
|
||||
_compile(code, av[2], flags)
|
||||
code[skipno] = _len(code) - skipno
|
||||
else:
|
||||
code[skipyes] = _len(code) - skipyes + 1
|
||||
else:
|
||||
raise error("internal: unsupported operand type %r" % (op,))
|
||||
|
||||
def _compile_charset(charset, flags, code):
|
||||
# compile charset subprogram
|
||||
emit = code.append
|
||||
for op, av in charset:
|
||||
emit(op)
|
||||
if op is NEGATE:
|
||||
pass
|
||||
elif op is LITERAL:
|
||||
emit(av)
|
||||
elif op is RANGE or op is RANGE_UNI_IGNORE:
|
||||
emit(av[0])
|
||||
emit(av[1])
|
||||
elif op is CHARSET:
|
||||
code.extend(av)
|
||||
elif op is BIGCHARSET:
|
||||
code.extend(av)
|
||||
elif op is CATEGORY:
|
||||
if flags & SRE_FLAG_LOCALE:
|
||||
emit(CH_LOCALE[av])
|
||||
elif flags & SRE_FLAG_UNICODE:
|
||||
emit(CH_UNICODE[av])
|
||||
else:
|
||||
emit(av)
|
||||
else:
|
||||
raise error("internal: unsupported set operator %r" % (op,))
|
||||
emit(FAILURE)
|
||||
|
||||
def _optimize_charset(charset, iscased=None, fixup=None, fixes=None):
|
||||
# internal: optimize character set
|
||||
out = []
|
||||
tail = []
|
||||
charmap = bytearray(256)
|
||||
hascased = False
|
||||
for op, av in charset:
|
||||
while True:
|
||||
try:
|
||||
if op is LITERAL:
|
||||
if fixup:
|
||||
lo = fixup(av)
|
||||
charmap[lo] = 1
|
||||
if fixes and lo in fixes:
|
||||
for k in fixes[lo]:
|
||||
charmap[k] = 1
|
||||
if not hascased and iscased(av):
|
||||
hascased = True
|
||||
else:
|
||||
charmap[av] = 1
|
||||
elif op is RANGE:
|
||||
r = range(av[0], av[1]+1)
|
||||
if fixup:
|
||||
if fixes:
|
||||
for i in map(fixup, r):
|
||||
charmap[i] = 1
|
||||
if i in fixes:
|
||||
for k in fixes[i]:
|
||||
charmap[k] = 1
|
||||
else:
|
||||
for i in map(fixup, r):
|
||||
charmap[i] = 1
|
||||
if not hascased:
|
||||
hascased = any(map(iscased, r))
|
||||
else:
|
||||
for i in r:
|
||||
charmap[i] = 1
|
||||
elif op is NEGATE:
|
||||
out.append((op, av))
|
||||
else:
|
||||
tail.append((op, av))
|
||||
except IndexError:
|
||||
if len(charmap) == 256:
|
||||
# character set contains non-UCS1 character codes
|
||||
charmap += b'\0' * 0xff00
|
||||
continue
|
||||
# Character set contains non-BMP character codes.
|
||||
if fixup:
|
||||
hascased = True
|
||||
# There are only two ranges of cased non-BMP characters:
|
||||
# 10400-1044F (Deseret) and 118A0-118DF (Warang Citi),
|
||||
# and for both ranges RANGE_UNI_IGNORE works.
|
||||
if op is RANGE:
|
||||
op = RANGE_UNI_IGNORE
|
||||
tail.append((op, av))
|
||||
break
|
||||
|
||||
# compress character map
|
||||
runs = []
|
||||
q = 0
|
||||
while True:
|
||||
p = charmap.find(1, q)
|
||||
if p < 0:
|
||||
break
|
||||
if len(runs) >= 2:
|
||||
runs = None
|
||||
break
|
||||
q = charmap.find(0, p)
|
||||
if q < 0:
|
||||
runs.append((p, len(charmap)))
|
||||
break
|
||||
runs.append((p, q))
|
||||
if runs is not None:
|
||||
# use literal/range
|
||||
for p, q in runs:
|
||||
if q - p == 1:
|
||||
out.append((LITERAL, p))
|
||||
else:
|
||||
out.append((RANGE, (p, q - 1)))
|
||||
out += tail
|
||||
# if the case was changed or new representation is more compact
|
||||
if hascased or len(out) < len(charset):
|
||||
return out, hascased
|
||||
# else original character set is good enough
|
||||
return charset, hascased
|
||||
|
||||
# use bitmap
|
||||
if len(charmap) == 256:
|
||||
data = _mk_bitmap(charmap)
|
||||
out.append((CHARSET, data))
|
||||
out += tail
|
||||
return out, hascased
|
||||
|
||||
# To represent a big charset, first a bitmap of all characters in the
|
||||
# set is constructed. Then, this bitmap is sliced into chunks of 256
|
||||
# characters, duplicate chunks are eliminated, and each chunk is
|
||||
# given a number. In the compiled expression, the charset is
|
||||
# represented by a 32-bit word sequence, consisting of one word for
|
||||
# the number of different chunks, a sequence of 256 bytes (64 words)
|
||||
# of chunk numbers indexed by their original chunk position, and a
|
||||
# sequence of 256-bit chunks (8 words each).
|
||||
|
||||
# Compression is normally good: in a typical charset, large ranges of
|
||||
# Unicode will be either completely excluded (e.g. if only cyrillic
|
||||
# letters are to be matched), or completely included (e.g. if large
|
||||
# subranges of Kanji match). These ranges will be represented by
|
||||
# chunks of all one-bits or all zero-bits.
|
||||
|
||||
# Matching can be also done efficiently: the more significant byte of
|
||||
# the Unicode character is an index into the chunk number, and the
|
||||
# less significant byte is a bit index in the chunk (just like the
|
||||
# CHARSET matching).
|
||||
|
||||
charmap = bytes(charmap) # should be hashable
|
||||
comps = {}
|
||||
mapping = bytearray(256)
|
||||
block = 0
|
||||
data = bytearray()
|
||||
for i in range(0, 65536, 256):
|
||||
chunk = charmap[i: i + 256]
|
||||
if chunk in comps:
|
||||
mapping[i // 256] = comps[chunk]
|
||||
else:
|
||||
mapping[i // 256] = comps[chunk] = block
|
||||
block += 1
|
||||
data += chunk
|
||||
data = _mk_bitmap(data)
|
||||
data[0:0] = [block] + _bytes_to_codes(mapping)
|
||||
out.append((BIGCHARSET, data))
|
||||
out += tail
|
||||
return out, hascased
|
||||
|
||||
_CODEBITS = _sre.CODESIZE * 8
|
||||
MAXCODE = (1 << _CODEBITS) - 1
|
||||
_BITS_TRANS = b'0' + b'1' * 255
|
||||
def _mk_bitmap(bits, _CODEBITS=_CODEBITS, _int=int):
|
||||
s = bits.translate(_BITS_TRANS)[::-1]
|
||||
return [_int(s[i - _CODEBITS: i], 2)
|
||||
for i in range(len(s), 0, -_CODEBITS)]
|
||||
|
||||
def _bytes_to_codes(b):
|
||||
# Convert block indices to word array
|
||||
a = memoryview(b).cast('I')
|
||||
assert a.itemsize == _sre.CODESIZE
|
||||
assert len(a) * a.itemsize == len(b)
|
||||
return a.tolist()
|
||||
|
||||
def _simple(p):
|
||||
# check if this subpattern is a "simple" operator
|
||||
if len(p) != 1:
|
||||
return False
|
||||
op, av = p[0]
|
||||
if op is SUBPATTERN:
|
||||
return av[0] is None and _simple(av[-1])
|
||||
return op in _UNIT_CODES
|
||||
|
||||
def _generate_overlap_table(prefix):
|
||||
"""
|
||||
Generate an overlap table for the following prefix.
|
||||
An overlap table is a table of the same size as the prefix which
|
||||
informs about the potential self-overlap for each index in the prefix:
|
||||
- if overlap[i] == 0, prefix[i:] can't overlap prefix[0:...]
|
||||
- if overlap[i] == k with 0 < k <= i, prefix[i-k+1:i+1] overlaps with
|
||||
prefix[0:k]
|
||||
"""
|
||||
table = [0] * len(prefix)
|
||||
for i in range(1, len(prefix)):
|
||||
idx = table[i - 1]
|
||||
while prefix[i] != prefix[idx]:
|
||||
if idx == 0:
|
||||
table[i] = 0
|
||||
break
|
||||
idx = table[idx - 1]
|
||||
else:
|
||||
table[i] = idx + 1
|
||||
return table
|
||||
|
||||
def _get_iscased(flags):
|
||||
if not flags & SRE_FLAG_IGNORECASE:
|
||||
return None
|
||||
elif flags & SRE_FLAG_UNICODE:
|
||||
return _sre.unicode_iscased
|
||||
else:
|
||||
return _sre.ascii_iscased
|
||||
|
||||
def _get_literal_prefix(pattern, flags):
|
||||
# look for literal prefix
|
||||
prefix = []
|
||||
prefixappend = prefix.append
|
||||
prefix_skip = None
|
||||
iscased = _get_iscased(flags)
|
||||
for op, av in pattern.data:
|
||||
if op is LITERAL:
|
||||
if iscased and iscased(av):
|
||||
break
|
||||
prefixappend(av)
|
||||
elif op is SUBPATTERN:
|
||||
group, add_flags, del_flags, p = av
|
||||
flags1 = _combine_flags(flags, add_flags, del_flags)
|
||||
if flags1 & SRE_FLAG_IGNORECASE and flags1 & SRE_FLAG_LOCALE:
|
||||
break
|
||||
prefix1, prefix_skip1, got_all = _get_literal_prefix(p, flags1)
|
||||
if prefix_skip is None:
|
||||
if group is not None:
|
||||
prefix_skip = len(prefix)
|
||||
elif prefix_skip1 is not None:
|
||||
prefix_skip = len(prefix) + prefix_skip1
|
||||
prefix.extend(prefix1)
|
||||
if not got_all:
|
||||
break
|
||||
else:
|
||||
break
|
||||
else:
|
||||
return prefix, prefix_skip, True
|
||||
return prefix, prefix_skip, False
|
||||
|
||||
def _get_charset_prefix(pattern, flags):
|
||||
while True:
|
||||
if not pattern.data:
|
||||
return None
|
||||
op, av = pattern.data[0]
|
||||
if op is not SUBPATTERN:
|
||||
break
|
||||
group, add_flags, del_flags, pattern = av
|
||||
flags = _combine_flags(flags, add_flags, del_flags)
|
||||
if flags & SRE_FLAG_IGNORECASE and flags & SRE_FLAG_LOCALE:
|
||||
return None
|
||||
|
||||
iscased = _get_iscased(flags)
|
||||
if op is LITERAL:
|
||||
if iscased and iscased(av):
|
||||
return None
|
||||
return [(op, av)]
|
||||
elif op is BRANCH:
|
||||
charset = []
|
||||
charsetappend = charset.append
|
||||
for p in av[1]:
|
||||
if not p:
|
||||
return None
|
||||
op, av = p[0]
|
||||
if op is LITERAL and not (iscased and iscased(av)):
|
||||
charsetappend((op, av))
|
||||
else:
|
||||
return None
|
||||
return charset
|
||||
elif op is IN:
|
||||
charset = av
|
||||
if iscased:
|
||||
for op, av in charset:
|
||||
if op is LITERAL:
|
||||
if iscased(av):
|
||||
return None
|
||||
elif op is RANGE:
|
||||
if av[1] > 0xffff:
|
||||
return None
|
||||
if any(map(iscased, range(av[0], av[1]+1))):
|
||||
return None
|
||||
return charset
|
||||
return None
|
||||
|
||||
def _compile_info(code, pattern, flags):
|
||||
# internal: compile an info block. in the current version,
|
||||
# this contains min/max pattern width, and an optional literal
|
||||
# prefix or a character map
|
||||
lo, hi = pattern.getwidth()
|
||||
if hi > MAXCODE:
|
||||
hi = MAXCODE
|
||||
if lo == 0:
|
||||
code.extend([INFO, 4, 0, lo, hi])
|
||||
return
|
||||
# look for a literal prefix
|
||||
prefix = []
|
||||
prefix_skip = 0
|
||||
charset = [] # not used
|
||||
if not (flags & SRE_FLAG_IGNORECASE and flags & SRE_FLAG_LOCALE):
|
||||
# look for literal prefix
|
||||
prefix, prefix_skip, got_all = _get_literal_prefix(pattern, flags)
|
||||
# if no prefix, look for charset prefix
|
||||
if not prefix:
|
||||
charset = _get_charset_prefix(pattern, flags)
|
||||
## if prefix:
|
||||
## print("*** PREFIX", prefix, prefix_skip)
|
||||
## if charset:
|
||||
## print("*** CHARSET", charset)
|
||||
# add an info block
|
||||
emit = code.append
|
||||
emit(INFO)
|
||||
skip = len(code); emit(0)
|
||||
# literal flag
|
||||
mask = 0
|
||||
if prefix:
|
||||
mask = SRE_INFO_PREFIX
|
||||
if prefix_skip is None and got_all:
|
||||
mask = mask | SRE_INFO_LITERAL
|
||||
elif charset:
|
||||
mask = mask | SRE_INFO_CHARSET
|
||||
emit(mask)
|
||||
# pattern length
|
||||
if lo < MAXCODE:
|
||||
emit(lo)
|
||||
else:
|
||||
emit(MAXCODE)
|
||||
prefix = prefix[:MAXCODE]
|
||||
emit(min(hi, MAXCODE))
|
||||
# add literal prefix
|
||||
if prefix:
|
||||
emit(len(prefix)) # length
|
||||
if prefix_skip is None:
|
||||
prefix_skip = len(prefix)
|
||||
emit(prefix_skip) # skip
|
||||
code.extend(prefix)
|
||||
# generate overlap table
|
||||
code.extend(_generate_overlap_table(prefix))
|
||||
elif charset:
|
||||
charset, hascased = _optimize_charset(charset)
|
||||
assert not hascased
|
||||
_compile_charset(charset, flags, code)
|
||||
code[skip] = len(code) - skip
|
||||
|
||||
def isstring(obj):
|
||||
return isinstance(obj, (str, bytes))
|
||||
|
||||
def _code(p, flags):
|
||||
|
||||
flags = p.state.flags | flags
|
||||
code = []
|
||||
|
||||
# compile info block
|
||||
_compile_info(code, p, flags)
|
||||
|
||||
# compile the pattern
|
||||
_compile(code, p.data, flags)
|
||||
|
||||
code.append(SUCCESS)
|
||||
|
||||
return code
|
||||
|
||||
def _hex_code(code):
|
||||
return '[%s]' % ', '.join('%#0*x' % (_sre.CODESIZE*2+2, x) for x in code)
|
||||
|
||||
def dis(code):
|
||||
import sys
|
||||
|
||||
labels = set()
|
||||
level = 0
|
||||
offset_width = len(str(len(code) - 1))
|
||||
|
||||
def dis_(start, end):
|
||||
def print_(*args, to=None):
|
||||
if to is not None:
|
||||
labels.add(to)
|
||||
args += ('(to %d)' % (to,),)
|
||||
print('%*d%s ' % (offset_width, start, ':' if start in labels else '.'),
|
||||
end=' '*(level-1))
|
||||
print(*args)
|
||||
|
||||
def print_2(*args):
|
||||
print(end=' '*(offset_width + 2*level))
|
||||
print(*args)
|
||||
|
||||
nonlocal level
|
||||
level += 1
|
||||
i = start
|
||||
while i < end:
|
||||
start = i
|
||||
op = code[i]
|
||||
i += 1
|
||||
op = OPCODES[op]
|
||||
if op in (SUCCESS, FAILURE, ANY, ANY_ALL,
|
||||
MAX_UNTIL, MIN_UNTIL, NEGATE):
|
||||
print_(op)
|
||||
elif op in (LITERAL, NOT_LITERAL,
|
||||
LITERAL_IGNORE, NOT_LITERAL_IGNORE,
|
||||
LITERAL_UNI_IGNORE, NOT_LITERAL_UNI_IGNORE,
|
||||
LITERAL_LOC_IGNORE, NOT_LITERAL_LOC_IGNORE):
|
||||
arg = code[i]
|
||||
i += 1
|
||||
print_(op, '%#02x (%r)' % (arg, chr(arg)))
|
||||
elif op is AT:
|
||||
arg = code[i]
|
||||
i += 1
|
||||
arg = str(ATCODES[arg])
|
||||
assert arg[:3] == 'AT_'
|
||||
print_(op, arg[3:])
|
||||
elif op is CATEGORY:
|
||||
arg = code[i]
|
||||
i += 1
|
||||
arg = str(CHCODES[arg])
|
||||
assert arg[:9] == 'CATEGORY_'
|
||||
print_(op, arg[9:])
|
||||
elif op in (IN, IN_IGNORE, IN_UNI_IGNORE, IN_LOC_IGNORE):
|
||||
skip = code[i]
|
||||
print_(op, skip, to=i+skip)
|
||||
dis_(i+1, i+skip)
|
||||
i += skip
|
||||
elif op in (RANGE, RANGE_UNI_IGNORE):
|
||||
lo, hi = code[i: i+2]
|
||||
i += 2
|
||||
print_(op, '%#02x %#02x (%r-%r)' % (lo, hi, chr(lo), chr(hi)))
|
||||
elif op is CHARSET:
|
||||
print_(op, _hex_code(code[i: i + 256//_CODEBITS]))
|
||||
i += 256//_CODEBITS
|
||||
elif op is BIGCHARSET:
|
||||
arg = code[i]
|
||||
i += 1
|
||||
mapping = list(b''.join(x.to_bytes(_sre.CODESIZE, sys.byteorder)
|
||||
for x in code[i: i + 256//_sre.CODESIZE]))
|
||||
print_(op, arg, mapping)
|
||||
i += 256//_sre.CODESIZE
|
||||
level += 1
|
||||
for j in range(arg):
|
||||
print_2(_hex_code(code[i: i + 256//_CODEBITS]))
|
||||
i += 256//_CODEBITS
|
||||
level -= 1
|
||||
elif op in (MARK, GROUPREF, GROUPREF_IGNORE, GROUPREF_UNI_IGNORE,
|
||||
GROUPREF_LOC_IGNORE):
|
||||
arg = code[i]
|
||||
i += 1
|
||||
print_(op, arg)
|
||||
elif op is JUMP:
|
||||
skip = code[i]
|
||||
print_(op, skip, to=i+skip)
|
||||
i += 1
|
||||
elif op is BRANCH:
|
||||
skip = code[i]
|
||||
print_(op, skip, to=i+skip)
|
||||
while skip:
|
||||
dis_(i+1, i+skip)
|
||||
i += skip
|
||||
start = i
|
||||
skip = code[i]
|
||||
if skip:
|
||||
print_('branch', skip, to=i+skip)
|
||||
else:
|
||||
print_(FAILURE)
|
||||
i += 1
|
||||
elif op in (REPEAT, REPEAT_ONE, MIN_REPEAT_ONE):
|
||||
skip, min, max = code[i: i+3]
|
||||
if max == MAXREPEAT:
|
||||
max = 'MAXREPEAT'
|
||||
print_(op, skip, min, max, to=i+skip)
|
||||
dis_(i+3, i+skip)
|
||||
i += skip
|
||||
elif op is GROUPREF_EXISTS:
|
||||
arg, skip = code[i: i+2]
|
||||
print_(op, arg, skip, to=i+skip)
|
||||
i += 2
|
||||
elif op in (ASSERT, ASSERT_NOT):
|
||||
skip, arg = code[i: i+2]
|
||||
print_(op, skip, arg, to=i+skip)
|
||||
dis_(i+2, i+skip)
|
||||
i += skip
|
||||
elif op is INFO:
|
||||
skip, flags, min, max = code[i: i+4]
|
||||
if max == MAXREPEAT:
|
||||
max = 'MAXREPEAT'
|
||||
print_(op, skip, bin(flags), min, max, to=i+skip)
|
||||
start = i+4
|
||||
if flags & SRE_INFO_PREFIX:
|
||||
prefix_len, prefix_skip = code[i+4: i+6]
|
||||
print_2(' prefix_skip', prefix_skip)
|
||||
start = i + 6
|
||||
prefix = code[start: start+prefix_len]
|
||||
print_2(' prefix',
|
||||
'[%s]' % ', '.join('%#02x' % x for x in prefix),
|
||||
'(%r)' % ''.join(map(chr, prefix)))
|
||||
start += prefix_len
|
||||
print_2(' overlap', code[start: start+prefix_len])
|
||||
start += prefix_len
|
||||
if flags & SRE_INFO_CHARSET:
|
||||
level += 1
|
||||
print_2('in')
|
||||
dis_(start, i+skip)
|
||||
level -= 1
|
||||
i += skip
|
||||
else:
|
||||
raise ValueError(op)
|
||||
|
||||
level -= 1
|
||||
|
||||
dis_(0, len(code))
|
||||
|
||||
|
||||
def compile(p, flags=0):
|
||||
# internal: convert pattern list to internal format
|
||||
|
||||
if isstring(p):
|
||||
pattern = p
|
||||
p = sre_parse.parse(p, flags)
|
||||
else:
|
||||
pattern = None
|
||||
|
||||
code = _code(p, flags)
|
||||
|
||||
if flags & SRE_FLAG_DEBUG:
|
||||
print()
|
||||
dis(code)
|
||||
|
||||
# map in either direction
|
||||
groupindex = p.state.groupdict
|
||||
indexgroup = [None] * p.state.groups
|
||||
for k, i in groupindex.items():
|
||||
indexgroup[i] = k
|
||||
|
||||
return _sre.compile(
|
||||
pattern, flags | p.state.flags, code,
|
||||
p.state.groups-1,
|
||||
groupindex, tuple(indexgroup)
|
||||
)
|
||||
from re import _compiler as _
|
||||
globals().update({k: v for k, v in vars(_).items() if k[:2] != '__'})
|
||||
Vendored
+6
-214
@@ -1,218 +1,10 @@
|
||||
#
|
||||
# Secret Labs' Regular Expression Engine
|
||||
#
|
||||
# various symbols used by the regular expression engine.
|
||||
# run this script to update the _sre include files!
|
||||
#
|
||||
# Copyright (c) 1998-2001 by Secret Labs AB. All rights reserved.
|
||||
#
|
||||
# See the sre.py file for information on usage and redistribution.
|
||||
#
|
||||
import warnings
|
||||
warnings.warn(f"module {__name__!r} is deprecated",
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
|
||||
"""Internal support module for sre"""
|
||||
|
||||
# update when constants are added or removed
|
||||
|
||||
MAGIC = 20171005
|
||||
|
||||
from _sre import MAXREPEAT, MAXGROUPS
|
||||
|
||||
# SRE standard exception (access as sre.error)
|
||||
# should this really be here?
|
||||
|
||||
class error(Exception):
|
||||
"""Exception raised for invalid regular expressions.
|
||||
|
||||
Attributes:
|
||||
|
||||
msg: The unformatted error message
|
||||
pattern: The regular expression pattern
|
||||
pos: The index in the pattern where compilation failed (may be None)
|
||||
lineno: The line corresponding to pos (may be None)
|
||||
colno: The column corresponding to pos (may be None)
|
||||
"""
|
||||
|
||||
__module__ = 're'
|
||||
|
||||
def __init__(self, msg, pattern=None, pos=None):
|
||||
self.msg = msg
|
||||
self.pattern = pattern
|
||||
self.pos = pos
|
||||
if pattern is not None and pos is not None:
|
||||
msg = '%s at position %d' % (msg, pos)
|
||||
if isinstance(pattern, str):
|
||||
newline = '\n'
|
||||
else:
|
||||
newline = b'\n'
|
||||
self.lineno = pattern.count(newline, 0, pos) + 1
|
||||
self.colno = pos - pattern.rfind(newline, 0, pos)
|
||||
if newline in pattern:
|
||||
msg = '%s (line %d, column %d)' % (msg, self.lineno, self.colno)
|
||||
else:
|
||||
self.lineno = self.colno = None
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class _NamedIntConstant(int):
|
||||
def __new__(cls, value, name):
|
||||
self = super(_NamedIntConstant, cls).__new__(cls, value)
|
||||
self.name = name
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
MAXREPEAT = _NamedIntConstant(MAXREPEAT, 'MAXREPEAT')
|
||||
|
||||
def _makecodes(names):
|
||||
names = names.strip().split()
|
||||
items = [_NamedIntConstant(i, name) for i, name in enumerate(names)]
|
||||
globals().update({item.name: item for item in items})
|
||||
return items
|
||||
|
||||
# operators
|
||||
# failure=0 success=1 (just because it looks better that way :-)
|
||||
OPCODES = _makecodes("""
|
||||
FAILURE SUCCESS
|
||||
|
||||
ANY ANY_ALL
|
||||
ASSERT ASSERT_NOT
|
||||
AT
|
||||
BRANCH
|
||||
CALL
|
||||
CATEGORY
|
||||
CHARSET BIGCHARSET
|
||||
GROUPREF GROUPREF_EXISTS
|
||||
IN
|
||||
INFO
|
||||
JUMP
|
||||
LITERAL
|
||||
MARK
|
||||
MAX_UNTIL
|
||||
MIN_UNTIL
|
||||
NOT_LITERAL
|
||||
NEGATE
|
||||
RANGE
|
||||
REPEAT
|
||||
REPEAT_ONE
|
||||
SUBPATTERN
|
||||
MIN_REPEAT_ONE
|
||||
|
||||
GROUPREF_IGNORE
|
||||
IN_IGNORE
|
||||
LITERAL_IGNORE
|
||||
NOT_LITERAL_IGNORE
|
||||
|
||||
GROUPREF_LOC_IGNORE
|
||||
IN_LOC_IGNORE
|
||||
LITERAL_LOC_IGNORE
|
||||
NOT_LITERAL_LOC_IGNORE
|
||||
|
||||
GROUPREF_UNI_IGNORE
|
||||
IN_UNI_IGNORE
|
||||
LITERAL_UNI_IGNORE
|
||||
NOT_LITERAL_UNI_IGNORE
|
||||
RANGE_UNI_IGNORE
|
||||
|
||||
MIN_REPEAT MAX_REPEAT
|
||||
""")
|
||||
del OPCODES[-2:] # remove MIN_REPEAT and MAX_REPEAT
|
||||
|
||||
# positions
|
||||
ATCODES = _makecodes("""
|
||||
AT_BEGINNING AT_BEGINNING_LINE AT_BEGINNING_STRING
|
||||
AT_BOUNDARY AT_NON_BOUNDARY
|
||||
AT_END AT_END_LINE AT_END_STRING
|
||||
|
||||
AT_LOC_BOUNDARY AT_LOC_NON_BOUNDARY
|
||||
|
||||
AT_UNI_BOUNDARY AT_UNI_NON_BOUNDARY
|
||||
""")
|
||||
|
||||
# categories
|
||||
CHCODES = _makecodes("""
|
||||
CATEGORY_DIGIT CATEGORY_NOT_DIGIT
|
||||
CATEGORY_SPACE CATEGORY_NOT_SPACE
|
||||
CATEGORY_WORD CATEGORY_NOT_WORD
|
||||
CATEGORY_LINEBREAK CATEGORY_NOT_LINEBREAK
|
||||
|
||||
CATEGORY_LOC_WORD CATEGORY_LOC_NOT_WORD
|
||||
|
||||
CATEGORY_UNI_DIGIT CATEGORY_UNI_NOT_DIGIT
|
||||
CATEGORY_UNI_SPACE CATEGORY_UNI_NOT_SPACE
|
||||
CATEGORY_UNI_WORD CATEGORY_UNI_NOT_WORD
|
||||
CATEGORY_UNI_LINEBREAK CATEGORY_UNI_NOT_LINEBREAK
|
||||
""")
|
||||
|
||||
|
||||
# replacement operations for "ignore case" mode
|
||||
OP_IGNORE = {
|
||||
LITERAL: LITERAL_IGNORE,
|
||||
NOT_LITERAL: NOT_LITERAL_IGNORE,
|
||||
}
|
||||
|
||||
OP_LOCALE_IGNORE = {
|
||||
LITERAL: LITERAL_LOC_IGNORE,
|
||||
NOT_LITERAL: NOT_LITERAL_LOC_IGNORE,
|
||||
}
|
||||
|
||||
OP_UNICODE_IGNORE = {
|
||||
LITERAL: LITERAL_UNI_IGNORE,
|
||||
NOT_LITERAL: NOT_LITERAL_UNI_IGNORE,
|
||||
}
|
||||
|
||||
AT_MULTILINE = {
|
||||
AT_BEGINNING: AT_BEGINNING_LINE,
|
||||
AT_END: AT_END_LINE
|
||||
}
|
||||
|
||||
AT_LOCALE = {
|
||||
AT_BOUNDARY: AT_LOC_BOUNDARY,
|
||||
AT_NON_BOUNDARY: AT_LOC_NON_BOUNDARY
|
||||
}
|
||||
|
||||
AT_UNICODE = {
|
||||
AT_BOUNDARY: AT_UNI_BOUNDARY,
|
||||
AT_NON_BOUNDARY: AT_UNI_NON_BOUNDARY
|
||||
}
|
||||
|
||||
CH_LOCALE = {
|
||||
CATEGORY_DIGIT: CATEGORY_DIGIT,
|
||||
CATEGORY_NOT_DIGIT: CATEGORY_NOT_DIGIT,
|
||||
CATEGORY_SPACE: CATEGORY_SPACE,
|
||||
CATEGORY_NOT_SPACE: CATEGORY_NOT_SPACE,
|
||||
CATEGORY_WORD: CATEGORY_LOC_WORD,
|
||||
CATEGORY_NOT_WORD: CATEGORY_LOC_NOT_WORD,
|
||||
CATEGORY_LINEBREAK: CATEGORY_LINEBREAK,
|
||||
CATEGORY_NOT_LINEBREAK: CATEGORY_NOT_LINEBREAK
|
||||
}
|
||||
|
||||
CH_UNICODE = {
|
||||
CATEGORY_DIGIT: CATEGORY_UNI_DIGIT,
|
||||
CATEGORY_NOT_DIGIT: CATEGORY_UNI_NOT_DIGIT,
|
||||
CATEGORY_SPACE: CATEGORY_UNI_SPACE,
|
||||
CATEGORY_NOT_SPACE: CATEGORY_UNI_NOT_SPACE,
|
||||
CATEGORY_WORD: CATEGORY_UNI_WORD,
|
||||
CATEGORY_NOT_WORD: CATEGORY_UNI_NOT_WORD,
|
||||
CATEGORY_LINEBREAK: CATEGORY_UNI_LINEBREAK,
|
||||
CATEGORY_NOT_LINEBREAK: CATEGORY_UNI_NOT_LINEBREAK
|
||||
}
|
||||
|
||||
# flags
|
||||
SRE_FLAG_TEMPLATE = 1 # template mode (disable backtracking)
|
||||
SRE_FLAG_IGNORECASE = 2 # case insensitive
|
||||
SRE_FLAG_LOCALE = 4 # honour system locale
|
||||
SRE_FLAG_MULTILINE = 8 # treat target as multiline string
|
||||
SRE_FLAG_DOTALL = 16 # treat target as a single string
|
||||
SRE_FLAG_UNICODE = 32 # use unicode "locale"
|
||||
SRE_FLAG_VERBOSE = 64 # ignore whitespace and comments
|
||||
SRE_FLAG_DEBUG = 128 # debugging
|
||||
SRE_FLAG_ASCII = 256 # use ascii "locale"
|
||||
|
||||
# flags for INFO primitive
|
||||
SRE_INFO_PREFIX = 1 # has prefix
|
||||
SRE_INFO_LITERAL = 2 # entire pattern is literal (given by prefix)
|
||||
SRE_INFO_CHARSET = 4 # pattern starts with character from given set
|
||||
from re import _constants as _
|
||||
globals().update({k: v for k, v in vars(_).items() if k[:2] != '__'})
|
||||
|
||||
if __name__ == "__main__":
|
||||
def dump(f, d, typ, int_t, prefix):
|
||||
|
||||
Vendored
+6
-1063
File diff suppressed because it is too large.
Load diff
Vendored
+30
-1
@@ -45,7 +45,7 @@ def capwords(s, sep=None):
|
||||
sep is used to split and join the words.
|
||||
|
||||
"""
|
||||
return (sep or ' ').join(x.capitalize() for x in s.split(sep))
|
||||
return (sep or ' ').join(map(str.capitalize, s.split(sep)))
|
||||
|
||||
|
||||
####################################################################
|
||||
@@ -141,6 +141,35 @@ class Template:
|
||||
self.pattern)
|
||||
return self.pattern.sub(convert, self.template)
|
||||
|
||||
def is_valid(self):
|
||||
for mo in self.pattern.finditer(self.template):
|
||||
if mo.group('invalid') is not None:
|
||||
return False
|
||||
if (mo.group('named') is None
|
||||
and mo.group('braced') is None
|
||||
and mo.group('escaped') is None):
|
||||
# If all the groups are None, there must be
|
||||
# another group we're not expecting
|
||||
raise ValueError('Unrecognized named group in pattern',
|
||||
self.pattern)
|
||||
return True
|
||||
|
||||
def get_identifiers(self):
|
||||
ids = []
|
||||
for mo in self.pattern.finditer(self.template):
|
||||
named = mo.group('named') or mo.group('braced')
|
||||
if named is not None and named not in ids:
|
||||
# add a named group only the first time it appears
|
||||
ids.append(named)
|
||||
elif (named is None
|
||||
and mo.group('invalid') is None
|
||||
and mo.group('escaped') is None):
|
||||
# If all the groups are None, there must be
|
||||
# another group we're not expecting
|
||||
raise ValueError('Unrecognized named group in pattern',
|
||||
self.pattern)
|
||||
return ids
|
||||
|
||||
# Initialize Template.pattern. __init_subclass__() is automatically called
|
||||
# only for subclasses, not for the Template class itself.
|
||||
Template.__init_subclass__()
|
||||
|
||||
Vendored
+618
-194
File diff suppressed because it is too large.
Load diff
Vendored
-4
@@ -475,8 +475,6 @@ class TestTemplate(unittest.TestCase):
|
||||
self.assertEqual(s.substitute(dict(who='tim', what='ham')),
|
||||
'tim likes to eat a bag of ham worth $100')
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_is_valid(self):
|
||||
eq = self.assertEqual
|
||||
s = Template('$who likes to eat a bag of ${what} worth $$100')
|
||||
@@ -498,8 +496,6 @@ class TestTemplate(unittest.TestCase):
|
||||
s = BadPattern('@bag.foo.who likes to eat a bag of @bag.what')
|
||||
self.assertRaises(ValueError, s.is_valid)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_get_identifiers(self):
|
||||
eq = self.assertEqual
|
||||
raises = self.assertRaises
|
||||
|
||||
+1
-7
@@ -36,6 +36,7 @@ rustpython-compiler-core = { workspace = true }
|
||||
rustpython-parser-core = { workspace = true }
|
||||
rustpython-literal = { workspace = true }
|
||||
rustpython-format = { workspace = true }
|
||||
rustpython-sre_engine = { workspace = true }
|
||||
|
||||
ascii = { workspace = true }
|
||||
ahash = { workspace = true }
|
||||
@@ -77,13 +78,6 @@ optional = "0.5.0"
|
||||
result-like = "0.4.5"
|
||||
timsort = "0.1.2"
|
||||
|
||||
# RustPython crates implementing functionality based on CPython
|
||||
sre-engine = "0.4.1"
|
||||
# to work on sre-engine locally or git version
|
||||
# sre-engine = { git = "https://github.com/RustPython/sre-engine", rev = "refs/pull/14/head" }
|
||||
# sre-engine = { git = "https://github.com/RustPython/sre-engine" }
|
||||
# sre-engine = { path = "../../sre-engine" }
|
||||
|
||||
## unicode stuff
|
||||
unicode_names2 = { workspace = true }
|
||||
# TODO: use unic for this; needed for title case:
|
||||
|
||||
+128
-61
@@ -5,13 +5,13 @@ mod _sre {
|
||||
use crate::{
|
||||
atomic_func,
|
||||
builtins::{
|
||||
PyCallableIterator, PyDictRef, PyGenericAlias, PyInt, PyList, PyStr, PyStrRef, PyTuple,
|
||||
PyTupleRef, PyTypeRef,
|
||||
PyCallableIterator, PyDictRef, PyGenericAlias, PyInt, PyList, PyListRef, PyStr,
|
||||
PyStrRef, PyTuple, PyTupleRef, PyTypeRef,
|
||||
},
|
||||
common::{ascii, hash::PyHash},
|
||||
convert::ToPyObject,
|
||||
function::{ArgCallable, OptionalArg, PosArgs, PyComparisonValue},
|
||||
protocol::{PyBuffer, PyMappingMethods},
|
||||
protocol::{PyBuffer, PyCallable, PyMappingMethods},
|
||||
stdlib::sys,
|
||||
types::{AsMapping, Comparable, Hashable, Representable},
|
||||
Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject,
|
||||
@@ -21,13 +21,13 @@ mod _sre {
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use itertools::Itertools;
|
||||
use num_traits::ToPrimitive;
|
||||
use sre_engine::{
|
||||
constants::SreFlag,
|
||||
engine::{lower_ascii, lower_unicode, upper_unicode, Request, SearchIter, State, StrDrive},
|
||||
use rustpython_sre_engine::{
|
||||
string::{lower_ascii, lower_unicode, upper_unicode},
|
||||
Request, SearchIter, SreFlag, State, StrDrive,
|
||||
};
|
||||
|
||||
#[pyattr]
|
||||
pub use sre_engine::{constants::SRE_MAGIC as MAGIC, CODESIZE, MAXGROUPS, MAXREPEAT};
|
||||
pub use rustpython_sre_engine::{CODESIZE, MAXGROUPS, MAXREPEAT, SRE_MAGIC as MAGIC};
|
||||
|
||||
#[pyfunction]
|
||||
fn getcodesize() -> usize {
|
||||
@@ -103,6 +103,58 @@ mod _sre {
|
||||
})
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "SRE_Template")]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct Template {
|
||||
literal: PyObjectRef,
|
||||
items: Vec<(usize, PyObjectRef)>,
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
impl Template {
|
||||
fn compile(
|
||||
pattern: PyRef<Pattern>,
|
||||
repl: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyRef<Self>> {
|
||||
let re = vm.import("re", None, 0)?;
|
||||
let func = re.get_attr("_compile_template", vm)?;
|
||||
let result = func.call((pattern, repl.clone()), vm)?;
|
||||
result
|
||||
.downcast::<Self>()
|
||||
.map_err(|_| vm.new_runtime_error("expected SRE_Template".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn template(
|
||||
_pattern: PyObjectRef,
|
||||
template: PyListRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Template> {
|
||||
let err = || vm.new_type_error("invalid template".to_owned());
|
||||
|
||||
let mut items = Vec::with_capacity(1);
|
||||
let v = template.borrow_vec();
|
||||
let literal = v.first().ok_or_else(err)?.clone();
|
||||
let trunks = v[1..].chunks_exact(2);
|
||||
|
||||
if !trunks.remainder().is_empty() {
|
||||
return Err(err());
|
||||
}
|
||||
|
||||
for trunk in trunks {
|
||||
let index: usize = trunk[0]
|
||||
.payload::<PyInt>()
|
||||
.ok_or_else(|| vm.new_type_error("expected usize".to_owned()))?
|
||||
.try_to_primitive(vm)?;
|
||||
items.push((index, trunk[1].clone()));
|
||||
}
|
||||
|
||||
Ok(Template { literal, items })
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct StringArgs {
|
||||
string: PyObjectRef,
|
||||
@@ -157,9 +209,9 @@ mod _sre {
|
||||
where
|
||||
F: FnOnce(&str) -> PyResult<R>,
|
||||
{
|
||||
let string = string
|
||||
.payload::<PyStr>()
|
||||
.ok_or_else(|| vm.new_type_error("expected string".to_owned()))?;
|
||||
let string = string.payload::<PyStr>().ok_or_else(|| {
|
||||
vm.new_type_error(format!("expected string got '{}'", string.class()))
|
||||
})?;
|
||||
f(string.as_str())
|
||||
}
|
||||
|
||||
@@ -184,10 +236,9 @@ mod _sre {
|
||||
with_sre_str!(zelf, &string.clone(), vm, |x| {
|
||||
let req = x.create_request(&zelf, pos, endpos);
|
||||
let mut state = State::default();
|
||||
state.pymatch(req);
|
||||
Ok(state
|
||||
.has_matched
|
||||
.then(|| Match::new(&state, zelf.clone(), string).into_ref(&vm.ctx)))
|
||||
.pymatch(&req)
|
||||
.then(|| Match::new(&mut state, zelf.clone(), string).into_ref(&vm.ctx)))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -201,9 +252,8 @@ mod _sre {
|
||||
let mut req = x.create_request(&zelf, string_args.pos, string_args.endpos);
|
||||
req.match_all = true;
|
||||
let mut state = State::default();
|
||||
state.pymatch(req);
|
||||
Ok(state.has_matched.then(|| {
|
||||
Match::new(&state, zelf.clone(), string_args.string).into_ref(&vm.ctx)
|
||||
Ok(state.pymatch(&req).then(|| {
|
||||
Match::new(&mut state, zelf.clone(), string_args.string).into_ref(&vm.ctx)
|
||||
}))
|
||||
})
|
||||
}
|
||||
@@ -217,9 +267,8 @@ mod _sre {
|
||||
with_sre_str!(zelf, &string_args.string.clone(), vm, |x| {
|
||||
let req = x.create_request(&zelf, string_args.pos, string_args.endpos);
|
||||
let mut state = State::default();
|
||||
state.search(req);
|
||||
Ok(state.has_matched.then(|| {
|
||||
Match::new(&state, zelf.clone(), string_args.string).into_ref(&vm.ctx)
|
||||
Ok(state.search(req).then(|| {
|
||||
Match::new(&mut state, zelf.clone(), string_args.string).into_ref(&vm.ctx)
|
||||
}))
|
||||
})
|
||||
}
|
||||
@@ -237,7 +286,7 @@ mod _sre {
|
||||
let mut iter = SearchIter { req, state };
|
||||
|
||||
while iter.next().is_some() {
|
||||
let m = Match::new(&iter.state, zelf.clone(), string_args.string.clone());
|
||||
let m = Match::new(&mut iter.state, zelf.clone(), string_args.string.clone());
|
||||
|
||||
let item = if zelf.groups == 0 || zelf.groups == 1 {
|
||||
m.get_slice(zelf.groups, s, vm)
|
||||
@@ -318,7 +367,7 @@ mod _sre {
|
||||
/* get segment before this match */
|
||||
splitlist.push(s.slice(last, iter.state.start, vm));
|
||||
|
||||
let m = Match::new(&iter.state, zelf.clone(), split_args.string.clone());
|
||||
let m = Match::new(&mut iter.state, zelf.clone(), split_args.string.clone());
|
||||
|
||||
// add groups (if any)
|
||||
for i in 1..=zelf.groups {
|
||||
@@ -326,7 +375,7 @@ mod _sre {
|
||||
}
|
||||
|
||||
n += 1;
|
||||
last = iter.state.string_position;
|
||||
last = iter.state.cursor.position;
|
||||
}
|
||||
|
||||
// get segment following last match (even if empty)
|
||||
@@ -365,21 +414,25 @@ mod _sre {
|
||||
count,
|
||||
} = sub_args;
|
||||
|
||||
let (is_callable, filter) = if repl.is_callable() {
|
||||
(true, repl)
|
||||
enum FilterType<'a> {
|
||||
Literal(PyObjectRef),
|
||||
Callable(PyCallable<'a>),
|
||||
Template(PyRef<Template>),
|
||||
}
|
||||
|
||||
let filter = if let Some(callable) = repl.to_callable() {
|
||||
FilterType::Callable(callable)
|
||||
} else {
|
||||
let is_template = if zelf.isbytes {
|
||||
Self::with_bytes(&repl, vm, |x| Ok(x.contains(&b'\\')))
|
||||
Self::with_bytes(&repl, vm, |x| Ok(x.contains(&b'\\')))?
|
||||
} else {
|
||||
Self::with_str(&repl, vm, |x| Ok(x.contains('\\')))
|
||||
}?;
|
||||
Self::with_str(&repl, vm, |x| Ok(x.contains('\\')))?
|
||||
};
|
||||
|
||||
if is_template {
|
||||
let re = vm.import("re", None, 0)?;
|
||||
let func = re.get_attr("_subx", vm)?;
|
||||
let filter = func.call((zelf.clone(), repl), vm)?;
|
||||
(filter.is_callable(), filter)
|
||||
FilterType::Template(Template::compile(zelf.clone(), repl, vm)?)
|
||||
} else {
|
||||
(false, repl)
|
||||
FilterType::Literal(repl)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -397,15 +450,29 @@ mod _sre {
|
||||
sublist.push(s.slice(last_pos, iter.state.start, vm));
|
||||
}
|
||||
|
||||
if is_callable {
|
||||
let m = Match::new(&iter.state, zelf.clone(), string.clone());
|
||||
let ret = filter.call((m.into_ref(&vm.ctx),), vm)?;
|
||||
sublist.push(ret);
|
||||
} else {
|
||||
sublist.push(filter.clone());
|
||||
}
|
||||
match &filter {
|
||||
FilterType::Literal(literal) => sublist.push(literal.clone()),
|
||||
FilterType::Callable(callable) => {
|
||||
let m = Match::new(&mut iter.state, zelf.clone(), string.clone())
|
||||
.into_ref(&vm.ctx);
|
||||
sublist.push(callable.invoke((m,), vm)?);
|
||||
}
|
||||
FilterType::Template(template) => {
|
||||
let m = Match::new(&mut iter.state, zelf.clone(), string.clone());
|
||||
// template.expand(m)?
|
||||
// let mut list = vec![template.literal.clone()];
|
||||
sublist.push(template.literal.clone());
|
||||
for (index, literal) in template.items.iter().cloned() {
|
||||
if let Some(item) = m.get_slice(index, s, vm) {
|
||||
sublist.push(item);
|
||||
}
|
||||
sublist.push(literal);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
last_pos = iter.state.string_position;
|
||||
|
||||
last_pos = iter.state.cursor.position;
|
||||
n += 1;
|
||||
}
|
||||
|
||||
@@ -528,17 +595,15 @@ mod _sre {
|
||||
|
||||
#[pyclass(with(AsMapping, Representable))]
|
||||
impl Match {
|
||||
pub(crate) fn new<S: StrDrive>(
|
||||
state: &State<S>,
|
||||
pattern: PyRef<Pattern>,
|
||||
string: PyObjectRef,
|
||||
) -> Self {
|
||||
let mut regs = vec![(state.start as isize, state.string_position as isize)];
|
||||
pub(crate) fn new(state: &mut State, pattern: PyRef<Pattern>, string: PyObjectRef) -> Self {
|
||||
let string_position = state.cursor.position;
|
||||
let marks = &state.marks;
|
||||
let mut regs = vec![(state.start as isize, string_position as isize)];
|
||||
for group in 0..pattern.groups {
|
||||
let mark_index = 2 * group;
|
||||
if mark_index + 1 < state.marks.len() {
|
||||
let start = state.marks[mark_index];
|
||||
let end = state.marks[mark_index + 1];
|
||||
if mark_index + 1 < marks.raw().len() {
|
||||
let start = marks.raw()[mark_index];
|
||||
let end = marks.raw()[mark_index + 1];
|
||||
if start.is_some() && end.is_some() {
|
||||
regs.push((start.unpack() as isize, end.unpack() as isize));
|
||||
continue;
|
||||
@@ -550,8 +615,8 @@ mod _sre {
|
||||
string,
|
||||
pattern,
|
||||
pos: state.start,
|
||||
endpos: state.string_position,
|
||||
lastindex: state.marks.last_index(),
|
||||
endpos: string_position,
|
||||
lastindex: marks.last_index(),
|
||||
regs,
|
||||
}
|
||||
}
|
||||
@@ -796,14 +861,15 @@ mod _sre {
|
||||
let mut req = s.create_request(&self.pattern, self.start.load(), self.end);
|
||||
let mut state = State::default();
|
||||
req.must_advance = self.must_advance.load();
|
||||
state.pymatch(req);
|
||||
let has_matched = state.pymatch(&req);
|
||||
|
||||
self.must_advance
|
||||
.store(state.string_position == state.start);
|
||||
self.start.store(state.string_position);
|
||||
.store(state.cursor.position == state.start);
|
||||
self.start.store(state.cursor.position);
|
||||
|
||||
Ok(state.has_matched.then(|| {
|
||||
Match::new(&state, self.pattern.clone(), self.string.clone()).into_ref(&vm.ctx)
|
||||
Ok(has_matched.then(|| {
|
||||
Match::new(&mut state, self.pattern.clone(), self.string.clone())
|
||||
.into_ref(&vm.ctx)
|
||||
}))
|
||||
})
|
||||
}
|
||||
@@ -818,14 +884,15 @@ mod _sre {
|
||||
let mut state = State::default();
|
||||
req.must_advance = self.must_advance.load();
|
||||
|
||||
state.search(req);
|
||||
let has_matched = state.search(req);
|
||||
|
||||
self.must_advance
|
||||
.store(state.string_position == state.start);
|
||||
self.start.store(state.string_position);
|
||||
.store(state.cursor.position == state.start);
|
||||
self.start.store(state.cursor.position);
|
||||
|
||||
Ok(state.has_matched.then(|| {
|
||||
Match::new(&state, self.pattern.clone(), self.string.clone()).into_ref(&vm.ctx)
|
||||
Ok(has_matched.then(|| {
|
||||
Match::new(&mut state, self.pattern.clone(), self.string.clone())
|
||||
.into_ref(&vm.ctx)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user