1717import re
1818import unicodedata
1919
20- from .expr import expr_lookbehind , expr_max_length , expr_neg_lookbehind , expr_to_literal , expr_to_regex
20+ from .expr import expr_is_par_safe , expr_lookbehind , expr_max_length , expr_neg_lookbehind , expr_to_literal , expr_to_regex , is_plain_string
2121
2222
2323class ExecutionError (ValueError ):
@@ -27,9 +27,30 @@ class ExecutionError(ValueError):
2727def _compile_parallel (subs : list [dict ]) -> tuple [re .Pattern [str ], dict [str , str ]]:
2828 """Compile one parallel group: longest-pattern-first alternation
2929 with a named group per sub; lookaround guards for before:/after:.
30- Plain-string patterns additionally feed the casing maps."""
30+
31+ An all-plain group compiles to a replace tree in Ruby, where a
32+ later duplicate from overwrites an earlier one; a guarded group
33+ takes the megaregexp, where the earliest equal-key rule wins."""
34+ guards = ("before" , "after" , "not_before" , "not_after" )
35+ all_plain = not any (sub .get (g ) for sub in subs for g in guards ) and all (
36+ sub .get ("result" ) is not None
37+ and expr_is_par_safe (sub ["pattern" ])
38+ and expr_is_par_safe (sub ["result" ])
39+ for sub in subs
40+ )
41+ drop : set [int ] = set ()
42+ if all_plain :
43+ last : dict [str , int ] = {}
44+ for i , sub in enumerate (subs ):
45+ if is_plain_string (sub ["pattern" ]):
46+ last [expr_to_literal (sub ["pattern" ])] = i
47+ for i , sub in enumerate (subs ):
48+ if is_plain_string (sub ["pattern" ]) and last [expr_to_literal (sub ["pattern" ])] != i :
49+ drop .add (i )
3150 indexed = []
3251 for i , sub in enumerate (subs ):
52+ if i in drop :
53+ continue
3354 pat = expr_to_regex (sub ["pattern" ])
3455 full = pat
3556 if sub .get ("before" ):
0 commit comments