Python Lark解析器中正则/.+?/无法匹配任意终端字符问题
问题
简述:我希望匹配任意内容,但定义的正则规则/.+?/无法正常生效,请问原因是什么?
我编写了如下极简语法与测试代码:
from lark import Lark, Tree parser: Lark = Lark(r""" rterm: "(___hole 0" anything ")" anything: /.+?/ %import common.ESCAPED_STRING %import common.SIGNED_NUMBER %import common.WS %ignore WS """, start='rterm') test_strings: list[str] = ["(___hole 0 (fun n : nat => ___hole 1 (___hole 2 eq_refl : 0 + n = n)))"] for test_string in test_strings: print(f'{test_string=}') tree: Tree = parser.parse(test_string) print(tree.pretty())
尝试解析上述测试字符串时,程序抛出如下错误:
Traceback (most recent call last): File "/Users/brandomiranda/miniconda/envs/iit_term_synthesis/lib/python3.9/site-packages/IPython/core/interactiveshell.py", line 3398, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-18-352bf581b4ee>", line 19, in <cell line: 17> tree: Tree = parser.parse(test_string) File "/Users/brandomiranda/miniconda/envs/iit_term_synthesis/lib/python3.9/site-packages/lark/lark.py", line 581, in parse return self.parser.parse(text, start=start, on_error=on_error) File "/Users/brandomiranda/miniconda/envs/iit_term_synthesis/lib/python3.9/site-packages/lark/parser_frontends.py", line 106, in parse return self.parser.parse(stream, chosen_start, **kw) File "/Users/brandomiranda/miniconda/envs/iit_term_synthesis/lib/python3.9/site-packages/lark/parsers/earley.py", line 297, in parse to_scan = self._parse(lexer, columns, to_scan, start_symbol) File "/Users/brandomiranda/miniconda/envs/iit_term_synthesis/lib/python3.9/site-packages/lark/parsers/xearley.py", line 144, in _parse to_scan = scan(i, to_scan) File "/Users/brandomiranda/miniconda/envs/iit_term_synthesis/lib/python3.9/site-packages/lark/parsers/xearley.py", line 118, in scan raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan}, lark.exceptions.UnexpectedCharacters: No terminal matches 'f' in the current parser context, at line 1 col 13 (___hole 0 (fun n : nat => ___hole 1 (___hole 2 eq_r ^ Expected one of: * RPAR
错误核心提示如下:
lark.exceptions.UnexpectedCharacters: No terminal matches 'f' in the current parser context, at line 1 col 13 (___hole 0 (fun n : nat => ___hole 1 (___hole 2 eq_r ^ Expected one of: * RPAR
该报错的核心困惑:正则.+?按预期应该可以匹配任意字符,但解析器提示无法匹配对应位置的字符'f'。
已调研内容
已检索查阅相关资料,以下两个相关问题的内容未解决疑惑:
- Lark官方仓库issue#257相关内容
- StackOverflow相关问题:Lark parser can't parse characters, even though they are defined in regex of rule:该问题报错类型与本次遇到的一致,按正则规则通配符
.本应匹配字符'f',但实际未匹配成功。 - 问题同步发布在Lark官方讨论区。
问题原因
- Lark默认词法切分的上下文无关特性:和普通正则里
.+?配合后缀标记的匹配逻辑不同,Lark默认的词法分析阶段会独立于语法规则切分终端,不会提前感知到anything后面需要跟随右括号)。非贪婪匹配.+?的最短合法匹配长度是1个字符,词法分析器在当前位置匹配到1个符合规则的字符后就会结束这个终端的匹配,将控制权交回解析器,此时解析器会按照语法定义期待后续出现右括号,碰到'f'自然会抛出匹配错误。 - 正则通配符的默认限制:Lark底层使用Python正则引擎,该引擎中
.默认不匹配换行符,如果待匹配内容包含换行,就算调整了匹配长度逻辑也会出现内容截断问题。 - 正则本身的能力边界:简单通配符正则无法处理嵌套括号结构,测试用例中
anything部分包含多层嵌套括号,如果要精准匹配和开头(___hole 0配对的闭合右括号,单靠通配符正则无法实现,需要递归规则支持。
修复方案
根据实际需求选择对应实现方式即可:
- 不需要处理嵌套括号,仅需要匹配到最近的右括号:将
anything的正则修改为/[^)]+/,匹配所有非右括号的字符即可正常工作。 - 需要匹配包含换行的任意内容直到字符串末尾的最后一个右括号:给正则添加DOTALL标志,写成
/(?s).+(?=\)$)/,其中(?s)用于开启DOTALL模式让.匹配包括换行在内的所有字符,正向预查(?=\)$)确保匹配到字符串末尾的右括号前停止。 - 需要精准匹配嵌套括号对应的闭合位置:不要使用通配符正则,为括号内容编写递归解析规则,示例语法如下:
rterm: "(___hole 0" exprs ")" exprs: (expr | /[^()]+/)* expr: "(" exprs ")"
该规则可以自动处理任意层级的括号嵌套,正确匹配到和开头配对的右括号。
内容的提问来源于stack exchange,提问作者Charlie Parker
相关产品推荐
相关产品推荐

