我试图用以下规则标记表达式:
可以通过在解析中对(正确的)结果进行后处理来完成这两个例外(在parens中标记)。结果将被送到递归下降解析器。
以下是一些试验,但未通过我所包含的单元测试。 find_all
函数最接近匹配,但仍然设法删除一些部分。我没有在下面的代码中使用re.split()
(它会保留空字符串),但我尝试了没有更好的运气。我希望正则表达式能够避免在我的代码中逐个字符地扫描字符串。
def tokenize_search(line):
token_pat = re.compile(r'({{)([^{(?!{)]*|[^}(?!})]*)(}})')
tokens = re.search(token_pat, line).groups()
return list (tokens)
def tokenize_findall(line):
token_pat = re.compile(r'({{)([^{(?!{)]*|[^}(?!})]*)(}})')
tokens = re.findall(token_pat, line)
return tokens
def check(a1, a2):
print(a1 == a2)
def main():
check(tokenize_search('{{}}'), ['{{', '}}'])
check(tokenize_search('aaa {{}}'), ['{{', '}}'])
check(tokenize_search('{{aaa}}'), ['{{', 'aaa', '}}'])
check(tokenize_search('{{aa}} {{bbb}}'), ['{{', 'aa', '}}', '{{', 'bbb', '}}'])
check(tokenize_search('{{aaa {{ bb }} }}'), ['{{', 'aaa ', '{{', ' bb ', '}}', '}}'])
check(tokenize_search('{{aa {{ bbb {{ c }} }} }}'), ['{{', 'aa ', '{{', ' bbb ', '{{', ' c ', '}}', '}}', '}}'])
check(tokenize_search('{{a{a}{{ b{b}b {{ c }} }} }}'), ['{{', 'a{a}', '{{', ' b{b}b ', '{{', ' c ', '}}', '}}', '}}'])
UPDATE
感谢Olivier提供的解决方案。如果我能更好地理解正则表达式的了解,我仍然希望正则表达式解决方案能够正常工作。如果我使用下面的tokenize_finditer
方法,它会通过测试,它所做的只是填充skipped
组中间的内容(除了我可以进行后处理以使代码更简单的空间)。所以我希望我可以在or
正则表达式中添加一个'({{)|(}})'
子句,说:“或者让任何字符后跟任何不符合'}}'或'{{'的字符。不幸的是,我无法成功写出这个匹配器。我已经看到正则表达式的例子甚至能够进行递归匹配,并且因为这不是递归的,所以听起来更可行。
def tokenize_finditer(line):
token_pat = re.compile(r'({{)|(}})')
result = []
if re.search(token_pat, line):
prev = len(line)
for match in re.finditer(token_pat, line):
start, end = match.span()
if start > prev:
expr = line[prev:start]
if not expr.isspace():
result.append(expr)
prev = end
result.append(match.group())
return result
这个问题不是一个parentheses matching problem,但它足够接近我建议不要试图用正则表达式来解决它。
因为您要做的是使用给定的分隔符对字符串进行分区,然后我们可以编写基于partition
函数的解决方案,并进行一些调整以适应所有规则。
import re
def partition(s, sep):
tokens = s.split(sep)
# Intersperse the separator betweem found tokens
partition = [sep] * (2 * len(tokens) - 1)
partition[::2] = tokens
# We remove empty and whitespace-only tokens
return [tk for tk in partition if tk and not tk.isspace()]
def tokenize_search(line):
# Only keep what is inside brackets
line = re.search(r'{{.*}}', line).group() or ''
return [tk for sub in partition(line, '{{') for tk in partition(sub, '}}')]
上面的代码通过了所有测试。您需要将该结果提供给解析器以检查括号匹配。
我相信OlivierMelançon的分区方法是可行的方法。但是,正则表达式仍有一些用途,例如:检查所讨论的模式是否正确平衡或从较大的字符串中提取平衡(如第二个例子所示)。
这样做需要递归正则表达式:
{{((?>(?:(?!{{|}}).)++|(?R))*+)}}
由于Python re模块不支持正则表达式递归,因此您需要依赖替代的regex模块来使用它。
要进一步处理匹配结果,您需要查看$1
中的内部部分,并一次更深入一个级别,例如\w+|{{((?>(?:(?!(?:{{|}})).)++|(?R))*+)}}
,但这很麻烦。
刚刚在推特上发了你的消息:)我知道我已经迟到了2个月,但是如果你有兴趣的话我会有一些新的想法。
我查看了这些示例,发现您可以通过匹配和捕获所有“{{”或“}}”或“{{}}对中间的令牌”来获得相同的效果。幸运的是,表达起来相当简单:
/({{|}}|(?:(?!{{|}})[^ ])+(?!({{(?2)*?}}|(?:(?!{{|}}).)*)*$))/g
On regex101 using your examples
“在{{}}对中间”是唯一棘手的部分。为此,我使用了这个负向前瞻来确保我们不在一个位置后跟一个平衡数量的(可能是嵌套的){{}}对,然后是字符串的结尾。对于均衡的输入,这将确保所有匹配的令牌都在{{}}对内。
现在,你问,“平衡输入”部分怎么样?如果输入无效,那么诸如“aaa}}”的示例将产生["aaa", "}}"]
作为结果。不理想。您可以单独验证输入;或者,如果你想将它变成一个无法解决的怪物,那么你可以选择这样的东西:
/(?:^(?!({{(?1)*?}}|(?:(?!{{|}}).)*)*+$)(*COMMIT)(*F))?({{|}}|(?:(?!{{|}})[^ ])+(?!({{(?3)*?}}|(?:(?!{{|}}).)*)*+$))/g
这真的只是为了表演。我同意推荐解析器或其他更易维护的工具的其他建议。但是,如果你看过我的博客,那么你就会明白我对这些怪物有亲和力:)