Pyparsing:在括号中解析唯一最长的匹配

时间:2017-09-01 16:27:08

标签: context-free-grammar pyparsing

我正在尝试解析以下几行:

command(grep -o '(' file.txt)
command(ls -1)

与pyparsing。命令不会延伸多行。该规则的最初想法是

cmd = "command(" + pp.OneOrMore(pp.Word(pp.printables)) + ")"

但由于pp.printables还包含(并且应该包含)右括号“)”pyparsing无法解析命令。我可以强制pyparsing匹配最长的命令字符串,以便后跟一个右括号吗?

1 个答案:

答案 0 :(得分:0)

要查看您的问题,我首先创建了一个包含示例文本,解析器和runTests调用的小脚本:

import pyparsing as pp

tests = """\
    command(grep -o '(' file.txt)
    command(ls -1)
    """

cmd = "command(" + pp.OneOrMore(pp.Word(pp.printables)) + ")"
cmd.runTests(tests)

正如你所说,由于终止“)”在OneOrMore重复中被包含在内,因此失败了:

command(grep -o '(' file.txt)
                             ^
FAIL: Expected ")" (at char 29), (line:1, col:30)

command(ls -1)
              ^
FAIL: Expected ")" (at char 14), (line:1, col:15)

runTests在这里非常有用,因为它会显示解析后的结果,或者输出解析器误入歧途的标记。)

这是因为pyparsing纯粹是从左到右,没有暗示前瞻。

最简单的立即解决方法是从可以构成单词的printables集中排除')':

cmd = "command(" + pp.OneOrMore(pp.Word(pp.printables, excludeChars=")")) + ")"

这给出了成功的输出:

command(grep -o '(' file.txt)
['command(', 'grep', '-o', "'('", 'file.txt', ')']

command(ls -1)
['command(', 'ls', '-1', ')']

但是如果我在测试中添加不同的测试字符串:

command(grep -o ')' file.txt)

')'被误认为是关闭权利paren:

command(grep -o ')' file.txt)
                  ^
FAIL: Expected end of text (at char 18), (line:1, col:19)

通常在将“读取到X”变量的pyparsing中包含表达式时,我们需要确保引号内的X不会被误解为实际的X.

执行此操作的一种方法是在匹配可打印字词之前通过查找引用的字符串来抢先匹配:

cmd = "command(" + pp.OneOrMore(pp.quotedString | 
                                pp.Word(pp.printables, excludeChars=")")) + ")"

现在我们引用的权利paren被正确地作为一个引用的字符串:

command(grep -o ')' file.txt)
['command(', 'grep', '-o', "')'", 'file.txt', ')']

但是仍然存在许多可能会使这个解析器出现问题的极端情况,因此使用pyparsing SkipTo表达式可能更简单:

cmd = "command(" + pp.SkipTo(")", ignore=pp.quotedString) + ")"
运行测试的

command(grep -o '(' file.txt)
['command(', "grep -o '(' file.txt", ')']

command(ls -1)
['command(', 'ls -1', ')']

command(grep -o ')' file.txt)
['command(', "grep -o ')' file.txt", ')']

请注意,我们还必须明确告诉SkipTo跳过可能在引用字符串中的任何“)”字符。此外,我们的命令参数的主体现在作为单个字符串返回。

如果你的命令体本身可能包含括号值,那么我们仍会绊倒它们。看看这个测试:

command(grep -x '|'.join(['(', ')']) file.txt)

runTests再一次告诉我们,我们被一个')'误导,我们不想以此结束:

command(grep -x '|'.join(['(', ')']) file.txt)
                                     ^
FAIL: Expected end of text (at char 37), (line:1, col:38)

您可以为')'添加前瞻,告诉SkipTo只匹配字符串结尾前的')':

cmd = "command(" + pp.SkipTo(")" + pp.FollowedBy(pp.StringEnd()), 
                             ignore=pp.quotedString) + ")"

但是使用这个解析器,我们几乎可以使用字符串索引,拆分和条带方法恢复到你能做的事情。

向您展示的一个最终版本使用了pyparsing的nestedExpr,这将帮助您解决参数列表中嵌套括号的情况:

cmd = "command" + pp.originalTextFor(pp.nestedExpr())

通常,nestedExpr会将解析后的内容作为字符串列表的嵌套列表返回,但是通过用originalTextFor包装它,我们得到原始值。另请注意,我们删除了'('from the opening'命令(',因为nestedExpr将使用它来解析其左括号,结果如下:

command(grep -o '(' file.txt)
['command', "(grep -o '(' file.txt)"]

command(ls -1)
['command', '(ls -1)']

command(grep -o ')' file.txt)
['command', "(grep -o ')' file.txt)"]

command(grep -x '|'.join(['(', ')']) file.txt)
['command', "(grep -x '|'.join(['(', ')']) file.txt)"]

最终,您采用的方法和您需要的解析器的复杂性将取决于您的解析器的目标。但是这些例子应该会给你一些如何从这里扩展的想法。