Lark Parser解析含特殊字符的无键布尔表达式失败问题排查
问题排查与修复:Lark解析含特殊字符的无键布尔表达式崩溃
问题描述
使用Lark库解析两类布尔表达式:
- 带键表达式:如
(A=(value1) OR B>(value2)) AND C<=(value3) - 无键表达式:如
(A OR B) AND C
解析普通表达式正常,但解析含*等特殊字符的无键表达式(如valu* OR text)时崩溃,提示无法识别特殊字符;但将该表达式放在带键结构中(如A=(valu* OR text) AND B=(value2))时可正常解析。
原始代码
from lark import Lark, Tree, Token rules = """ ?start: expr ?expr: link_or ?link_or: (link_or "or"i)? link_and ?link_and: (link_and "and"i)? ( NAME | cond_eq | cond_gt | cond_ge | cond_lt | cond_le ) ?cond_eq: KEY "=" const | "(" expr ")" ?cond_gt: KEY ">" const | "(" expr ")" ?cond_ge: KEY ">=" const | "(" expr ")" ?cond_lt: KEY "<" const | "(" expr ")" ?cond_le: KEY "<=" const | "(" expr ")" KEY: NAME ?const: INT -> int | string_raw -> string ?string_raw: /\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/ %import common.CNAME -> NAME %import common.WS_INLINE %import common.INT %ignore WS_INLINE """ parser = Lark(rules) for text in ("key1=(value1*) OR key2=(value2)", "key1 OR key2", "key1* OR key2"): print("text:", text) try: tree = parser.parse(text) print("parsed tree:", tree) except BaseException as e: print("Exception:", e) print()
输出结果
示例1:
"key1=(value1*) OR key2=(value2)"(解析正常)
text: key1=(value1*) OR key2=(value2) parsed tree: Tree(Token('RULE', 'link_or'), [Tree(Token('RULE', 'cond_eq'), [Token('KEY', 'key1'), Tree('string', [Token('__ANON_2', '(value1*)')])]), Tree(Token('RULE', 'cond_eq'), [Token('KEY', 'key2'), Tree('string', [Token('__ANON_2', '(value2)')])])])
示例2:
"key1 OR key2"(解析正常)
text: key1 OR key2 parsed tree: Tree(Token('RULE', 'link_or'), [Token('NAME', 'key1'), Token('NAME', 'key2')])
示例3:
"key1* OR key2"(解析失败)
text: key1* OR key2 Exception: No terminal matches '*' in the current parser context, at line 1 col 5 key1* OR key2 ^ Expected one of: * __ANON_1 * OR * LESSTHAN * AND * MORETHAN * __ANON_0 * EQUAL
问题分析
崩溃核心原因:
- 无键表达式的原子项依赖
NAME匹配,但NAME导入的是common.CNAME,仅支持字母、数字、下划线组成的标识符,无法匹配*这类特殊字符。 - 带键结构中的特殊字符能正常解析,是因为被
string_raw规则的正则表达式匹配(该规则允许括号内包含任意字符)。
修复方案
新增pattern规则匹配含特殊字符的无键原子项,替换link_and规则中的NAME:
修改后的代码
from lark import Lark, Tree, Token rules = """ ?start: expr ?expr: link_or ?link_or: (link_or "or"i)? link_and ?link_and: (link_and "and"i)? ( pattern | cond_eq | cond_gt | cond_ge | cond_lt | cond_le ) ?cond_eq: KEY "=" const | "(" expr ")" ?cond_gt: KEY ">" const | "(" expr ")" ?cond_ge: KEY ">=" const | "(" expr ")" ?cond_lt: KEY "<" const | "(" expr ")" ?cond_le: KEY "<=" const | "(" expr ")" KEY: NAME # 匹配含特殊字符的无键原子项,可根据需求扩展允许的字符 ?pattern: /[a-zA-Z0-9_*]+/ ?const: INT -> int | string_raw -> string ?string_raw: /\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/ %import common.CNAME -> NAME %import common.WS_INLINE %import common.INT %ignore WS_INLINE """ parser = Lark(rules) for text in ("key1=(value1*) OR key2=(value2)", "key1 OR key2", "key1* OR key2"): print("text:", text) try: tree = parser.parse(text) print("parsed tree:", tree) except BaseException as e: print("Exception:", e) print()
扩展说明
若需要支持更多特殊字符(如?、+),可修改pattern的正则表达式:
- 宽松匹配(排除空格、括号和逻辑运算符):
/[^ \t\n\r()&|]+/ - 指定允许的字符:
/[a-zA-Z0-9_*?+@-]+/
修改后三个示例均可正常解析,含特殊字符的无键表达式会被识别为pattern节点。
内容的提问来源于stack exchange,提问作者Zhihar
相关产品推荐
相关产品推荐

