如何使用pyparsing解析存在歧义的缩进代码块?
问题
需要解析如下格式的数据:
data = """\ map=1 sub=1 int=99 foo=bar sub=2 foo=bar int=99 bar=qux """
基于pyparsing的缩进语法示例编写了解析代码:
from pyparsing import * stmt = Forward() suite = IndentedBlock(stmt) identifier = Word(alphas, alphanums) key = Combine(identifier + "=" + Word(nums)) definition = key + suite rhs = Regex(r"[a-z0-9]+") lhs = identifier + Suppress("=") + rhs stmt << (definition | lhs) body = ZeroOrMore(stmt) # 执行解析 tree = body.parse_string(input_string) print(tree)
当前解析结果:
['map=1', ['sub=1', ['int=99', ['foo', 'bar']], 'sub=2', ['foo', 'bar', 'int=99', ['bar', 'qux']]]]
期望解析结果:
['map=1', ['sub=1', ['int', '99', 'foo', 'bar'], ['sub=2', ['foo', 'bar', 'int', '99', 'bar', 'qux']]]]
核心问题:map=1、sub=1这类带整数索引的映射定义,与int=99这类普通整数赋值语法完全一致,导致解析器误将普通整数赋值后的键值对识别为新的缩进块,如何消除该歧义?能否使用负向预查?
解决方案
要解决这个歧义,核心是明确区分“带缩进子块的映射定义”和“普通键值对”:前者后续会跟随缩进层级更深的子元素,后者则没有。以下是可行的实现方式:
方式一:用负向预查精准匹配
利用pyparsing的FollowedBy和~(负向匹配),让普通键值对仅匹配那些后续没有缩进块的场景,避免被误判为映射定义。
修改后的解析代码:
from pyparsing import * stmt = Forward() suite = IndentedBlock(stmt) identifier = Word(alphas, alphanums) # 定义带整数索引的映射键(如map=1、sub=1) key = Combine(identifier + "=" + Word(nums)) # 映射定义:键 + 缩进子块 definition = key + suite # 普通键值对:identifier=值(值支持字母/数字) rhs = Regex(r"[a-z0-9]+") # 关键:用负向匹配确保当前键值对后续没有缩进块(换行+至少1个空白符) lhs = ~FollowedBy(LineEnd() + White(" \t", min=1)) + identifier + Suppress("=") + rhs # 调整匹配优先级:先匹配映射定义,再匹配普通键值对 stmt << (definition | lhs) body = ZeroOrMore(stmt) # 测试解析 input_string = """\ map=1 sub=1 int=99 foo=bar sub=2 foo=bar int=99 bar=qux """ tree = body.parse_string(input_string) print(tree.asList())
这段代码中,~FollowedBy(LineEnd() + White(" \t", min=1))会检查当前位置之后是否是“换行+缩进”的结构,如果是,则不匹配普通键值对,确保只有真正的普通键值对(如int=99)会被lhs匹配,而带缩进子块的映射定义(如sub=1)会被definition正确识别。
方式二:通过语义动作修正结构
如果负向匹配的逻辑不好理解,也可以先按原有语法解析,再通过语义动作遍历解析树,将误判的结构修正。但这种方式需要额外的后处理步骤,不如负向匹配直接高效。
内容的提问来源于stack exchange,提问作者Soós Péter Levente
相关产品推荐
相关产品推荐

