PyParsing 中的递归下降不是那么简单

Not so simple recursive descent in PyParsing

我需要以下需要解析的测试用例(模式)的帮助(在 python 中):

IO_SET(BLOCK, key1, value1, key2, value2, ... ,keyn, valuen);

其中 BLOCKkeyidentifiersvalue 是标识符(宏定义)或数字或函数或数值表达式。

我可以相对容易地拆分它(即使有重复的 RE 组),除了 value 是一个函数的情况,例如

IO_SET(BLOCK, key1, function(1+2,3, val11), key2, val2, key3, (3U)+cVAL3); 

p.s。括号、分号和逗号周围允许有零个或多个空格。

可能这可以通过 pyparsing 来完成,但我遇到了很多问题,例如 value = Word(nums) 单词“1a23”被解析为 value = "1"

这是您的示例的解析器。您必须定义递归语法(使用 pyparsing Forward),因为函数调用可以具有本身就是函数调用的参数:

sample = """IO_SET(BLOCK, key1, function(1+2,3, val11), key2, val2, key3, (3U)+cVAL3);"""


from pyparsing import *

SEMI,LPAREN,RPAREN = map(Suppress,";()")
identifier = Combine(Optional(Word(nums+'_')) + Word(alphas, alphanums+'_'))
integer= Combine(Optional('-') + Word(nums))
realnum = Combine(integer.copy() + '.' + Optional(Word(nums)))

fn_call = Forward()
# this order is *critical*
value = realnum | fn_call | identifier | integer

expr = infixNotation(value,
            [
            (oneOf('* /'), 2, opAssoc.LEFT),
            (oneOf('+ -'), 2, opAssoc.LEFT),
            ])
fn_call <<= Group(identifier + LPAREN + Group(Optional(delimitedList(expr))) + RPAREN)


print value.parseString(sample).asList()

打印:

[['IO_SET', ['BLOCK', 'key1', ['function', [['1', '+', '2'], '3', 'val11']], 
            'key2', 'val2', 'key3', ['3U', '+', 'cVAL3']]]]

如评论中所述,值中表达式的顺序很重要。由于这种语言支持可以以数字字符开头的标识符,因此您必须在测试整数之前测试和标识符(否则前导数字将被解释为整数并且字符串的其余部分将被悬空)。

您可以尝试一些替代方法来依赖此顺序:

  • 使用 Or 运算符 ('^') 而不是 MatchFirst ('|'),它将尝试 所有 可能的替代方案并选择最长的匹配项 (可以在像这样的递归语法中无限递归)

  • 强制整数后跟一个分词符(使用 pyparsing 的 WordEnd() class)

HTH

编辑

这是一个更新版本,其中包含您的明确定义。由于您的整数形式有一个清晰的正则表达式,最简单的方法是使用 pyparsing Regex class;并通过此更改,我将 identifier 恢复为更传统的形式。我还在函数参数中添加了键值结构,但必须定义两种函数调用,因为您的参数函数调用不符合结构化参数列表。使用新的 pprint 方法可以更轻松地查看您的 arg 列表结构。

sample = """IO_SET(BLOCK, key1, function(1+2,3, val11), key2, val2, key3, (3U)+cVAL3);"""

from pyparsing import *

SEMI,LPAREN,RPAREN,COMMA = map(Suppress,";(),")
#identifier = Combine(Optional(Word(nums+'_')) + Word(alphas, alphanums+'_'))
identifier = Word(alphas, alphanums+'_')
#integer= Combine(Optional('-') + Word(nums))
integer = Regex(r"[+-]?\d+[Uu]?[Ll]?")
realnum = Combine(integer.copy() + '.' + Optional(Word(nums)))

fn_call1 = Forward()
fn_call2 = Forward()
# this order is *critical*
value = realnum | fn_call1 | fn_call2 | identifier | integer

expr = infixNotation(value,
            [
            (oneOf('* /'), 2, opAssoc.LEFT),
            (oneOf('+ -'), 2, opAssoc.LEFT),
            ])
key_value = Group(identifier + COMMA + expr)
kv_args = identifier + Optional(COMMA + delimitedList(key_value))
fn_call1 <<= Group(identifier + LPAREN + Group(kv_args) + RPAREN)
simple_args = Optional(delimitedList(expr))
fn_call2 <<= Group(identifier + LPAREN + Group(simple_args) + RPAREN)

value.parseString(sample).pprint()

打印:

[['IO_SET',
  ['BLOCK',
   ['key1', ['function', [['1', '+', '2'], '3', 'val11']]],
   ['key2', 'val2'],
   ['key3', ['3U', '+', 'cVAL3']]]]]