Optimization#
compile() rewrites a pattern into a cheaper one before handing it to the
engine. str(pattern) always shows the pattern as built; optimized() shows
what compile() uses.
>>> import remagic as rm
>>> pattern = rm.any_of("abc").one_or_more().non_capturing().one_or_more()
>>> str(pattern)
'(?:(?:a|b|c)+)+'
>>> str(pattern.optimized())
'[abc]++'
Safe level (default)#
The safe level only applies rewrites that keep every match, span and group
identical. It merges adjacent literals, characters and repeats, collapses
nested quantifiers, drops redundant groups, merges single-character
alternatives into a class, and turns a greedy repeat possessive when the text
after it can never start with a character it matches. That removes
catastrophic backtracking such as (?:a+)+c against aaaa…b.
>>> str((rm.exactly("a") | "b" | "c").optimized())
'[abc]'
>>> str(rm.exactly("a").times(2).non_capturing().times(3).optimized())
'aaaaaa'
>>> str((rm.DIGIT.one_or_more() + "x").optimized())
'\\d++x'
Capturing groups are kept, so group numbers never shift. unsafe.raw() patterns and
scoped flags are opaque, and flags such as re.IGNORECASE also disable the
rewrites that depend on knowing exactly which characters match.
Aggressive level#
optimize="aggressive" also factors common prefixes and reorders alternatives.
That can change which match is found first, so use it with fullmatch or
yes/no checks only.
>>> pattern = rm.exactly("foobar") | "foobaz"
>>> str(pattern.optimized("aggressive"))
'fooba[rz]'
>>> pattern.compile(optimize="aggressive").fullmatch("foobaz") is not None
True
Turning it off#
>>> rm.exactly("a").compile(optimize=False).pattern
'a'