PyParsing 中的递归下降不是那么简单
Not so simple recursive descent in PyParsing
我需要以下需要解析的测试用例(模式)的帮助(在 python 中):
IO_SET(BLOCK, key1, value1, key2, value2, ... ,keyn, valuen);
其中 BLOCK 和 key 是 identifiers 和 value 是标识符(宏定义)或数字或函数或数值表达式。
我可以相对容易地拆分它(即使有重复的 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']]]]]
我需要以下需要解析的测试用例(模式)的帮助(在 python 中):
IO_SET(BLOCK, key1, value1, key2, value2, ... ,keyn, valuen);
其中 BLOCK 和 key 是 identifiers 和 value 是标识符(宏定义)或数字或函数或数值表达式。
我可以相对容易地拆分它(即使有重复的 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']]]]]