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

时间:2015-03-20 16:30:05

标签: python regex pyparsing

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

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

其中 BLOCK key 标识符是标识符(宏定义)或数字或函数或数字表达。

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

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

P.S。允许括号,分号和逗号周围有零个或多个空格。

可能这可以通过pyparsing来完成,但是我遇到了很多问题,例如在遇到问题的情况下 value = Word(nums)一个字" 1a23"被解析为value = "1"

1 个答案:

答案 0 :(得分:2)

这是您的示例的解析器。你必须定义一个递归语法(使用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()类)

HTH

修改

这是一个更新版本,带有您明确的定义。因为你的整数形式有一个清晰的正则表达式,所以最容易使用pyparsing Regex类;通过这一改变,我将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']]]]]