SickGear/lib/hachoir/regex/parser.py
2023-02-09 13:41:15 +00:00

209 lines
6.2 KiB
Python

r"""
Parse string to create Regex object.
TODO:
- Support \: \001, \x00, \0, \ \[, \(, \{, etc.
- Support Python extensions: (?:...), (?P<name>...), etc.
- Support \<, \>, \s, \S, \w, \W, \Z <=> $, \d, \D, \A <=> ^, \b, \B, [[:space:]], etc.
"""
from hachoir.regex import (RegexString, RegexEmpty, RegexRepeat,
RegexDot, RegexWord, RegexStart, RegexEnd,
RegexRange, RegexRangeItem, RegexRangeCharacter)
import re
REGEX_COMMAND_CHARACTERS = '.^$[](){}|+?*\\'
def parseRange(text, start):
r"""
>>> parseRange('[a]b', 1)
(<RegexRange '[a]'>, 3)
>>> parseRange('[a-z]b', 1)
(<RegexRange '[a-z]'>, 5)
>>> parseRange('[^a-z-]b', 1)
(<RegexRange '[^a-z-]'>, 7)
>>> parseRange('[^]-]b', 1)
(<RegexRange '[^]-]'>, 5)
>>> parseRange(r'[\]abc]', 1)
(<RegexRange '[]a-c]'>, 7)
>>> parseRange(r'[a\-x]', 1)
(<RegexRange '[ax-]'>, 6)
"""
index = start
char_range = []
exclude = False
if text[index] == '^':
exclude = True
index += 1
if text[index] == ']':
char_range.append(RegexRangeCharacter(']'))
index += 1
while index < len(text) and text[index] != ']':
if index + 1 < len(text) \
and text[index] == '\\':
char_range.append(RegexRangeCharacter(text[index + 1]))
index += 2
elif index + 1 < len(text) \
and text[index] == '-' and text[index + 1] == ']':
break
elif index + 3 < len(text) \
and text[index + 1] == '-' \
and text[index + 2] != ']':
char_range.append(RegexRangeItem(
ord(text[index]), ord(text[index + 2])))
index += 3
else:
char_range.append(RegexRangeCharacter(text[index]))
index += 1
if index < len(text) and text[index] == '-':
char_range.append(RegexRangeCharacter('-'))
index += 1
if index == len(text) or text[index] != ']':
raise SyntaxError('Invalid range: %s' % text[start - 1:index])
return RegexRange(char_range, exclude), index + 1
def parseOr(text, start):
"""
>>> parseOr('(a)', 1)
(<RegexString 'a'>, 3)
>>> parseOr('(a|c)', 1)
(<RegexRange '[ac]'>, 5)
>>> parseOr(' (a|[bc]|d)', 2)
(<RegexRange '[a-d]'>, 11)
"""
index = start
# (?:...): Skip Python prefix '?:'
if text[index:index + 2] == '?:':
index += 2
if text[index] == '?':
raise NotImplementedError("Doesn't support Python extension (?...)")
regex = None
while True:
new_regex, index = _parse(text, index, "|)")
if regex:
regex = regex | new_regex
else:
regex = new_regex
if len(text) <= index:
raise SyntaxError('Missing closing parenthesis')
if text[index] == ')':
break
index += 1
index += 1
if regex is None:
regex = RegexEmpty()
return regex, index
REPEAT_REGEX = re.compile("([0-9]+)(,[0-9]*)?}")
def parseRepeat(text, start):
"""
>>> parseRepeat('a{0,1}b', 2)
(0, 1, 6)
>>> parseRepeat('a{12}', 2)
(12, 12, 5)
"""
match = REPEAT_REGEX.match(text, start)
if not match:
raise SyntaxError('Unable to parse repetition ' + text[start:])
rmin = int(match.group(1))
if match.group(2):
text = match.group(2)[1:]
if text:
rmax = int(text)
else:
rmax = None
else:
rmax = rmin
return (rmin, rmax, match.end(0))
CHAR_TO_FUNC = {'[': parseRange, '(': parseOr}
CHAR_TO_CLASS = {'.': RegexDot, '^': RegexStart, '$': RegexEnd}
CHAR_TO_REPEAT = {'*': (0, None), '?': (0, 1), '+': (1, None)}
def _parse(text, start=0, until=None):
if len(text) == start:
return RegexEmpty(), 0
index = start
regex = RegexEmpty()
last = None
while index < len(text):
char = text[index]
if until and char in until:
break
if char in REGEX_COMMAND_CHARACTERS:
if char in CHAR_TO_FUNC:
new_regex, index = CHAR_TO_FUNC[char](text, index + 1)
elif char in CHAR_TO_CLASS:
new_regex = CHAR_TO_CLASS[char]()
index += 1
elif char == '{':
rmin, rmax, index = parseRepeat(text, index + 1)
new_regex = RegexRepeat(last, rmin, rmax)
last = None
elif char in CHAR_TO_REPEAT:
rmin, rmax = CHAR_TO_REPEAT[char]
if last is None:
raise SyntaxError(
'Repetition character (%s) without previous expression' % text[index])
new_regex = RegexRepeat(last, rmin, rmax)
last = None
index += 1
elif char == "\\":
index += 1
if index == len(text):
raise SyntaxError(
"Antislash (\\) without escaped character")
char = text[index]
if char == 'b':
new_regex = RegexWord()
else:
if not(char in REGEX_COMMAND_CHARACTERS or char in " '"):
raise SyntaxError(
"Operator '\\%s' is not supported" % char)
new_regex = RegexString(char)
index += 1
else:
raise NotImplementedError(
"Operator '%s' is not supported" % char)
if last:
regex = regex + last
last = new_regex
else:
subtext = text[index]
index += 1
if last:
regex = regex + last
last = RegexString(subtext)
if last:
regex = regex + last
return regex, index
def parse(text):
r"""
>>> parse('')
<RegexEmpty ''>
>>> parse('abc')
<RegexString 'abc'>
>>> parse("chats?")
<RegexAnd 'chats?'>
>>> parse('[bc]d')
<RegexAnd '[bc]d'>
>>> parse("\\.")
<RegexString '\.'>
"""
regex, index = _parse(text)
assert index == len(text)
return regex
if __name__ == "__main__":
import doctest
doctest.testmod()