python – 在pyparsing中嵌套分隔列表而不会导致无限递归?

Pyparsing中有以下玩具语法:

import pyparsing as pp

or_tok = "or"
and_tok = "and"
lparen = pp.Suppress("(")
rparen = pp.Suppress(")")

Word = pp.Word(pp.alphas)("Word")

Phrase = pp.Forward()

And_Phrase = pp.Group(pp.delimitedList(Phrase, and_tok))("And_Phrase")
Or_Phrase = pp.Group(pp.delimitedList(Phrase, or_tok))("Or_Phrase")

Phrase << (pp.Optional(lparen) + (And_Phrase ^ Or_Phrase) + pp.Optional(rparen)) ^ Word

Expression = pp.OneOrMore(Word ^ Phrase)("Expression")


def test(text):
    output = Expression.parseString(text)
    print output.asXML()

但是,运行此程序将无限递归,这不是我想要的.相反,我希望我的语法能够处理嵌套短语,以便上述程序可以解析为与下面相同的东西:

>>> test("TestA and TestB and TestC or TestD")
<Expression>
    <And_Phrase>
        <Word>TestA</Word>
        <Word>TestB</Word>
        <Or_Phrase>
            <Word>TestC</Word>
            <Word>TestD</Word>
        </Or_Phrase>
    </And_Phrase>
</Expression>

我试图修改And_Phrase和Or_Phrase的定义,以便它们只匹配具有两个或更多元素的列表,但无法弄清楚如何执行此操作.

我也尝试过使用pyparsing.operatorPrecedence,但我不认为我做得对:

import pyparsing as pp

or_tok = "or"
and_tok = "and"
lparen = pp.Suppress("(")
rparen = pp.Suppress(")")

Word = pp.Word(pp.alphas)("Word")

Phrase = pp.Forward()

Phrase << Word ^ \
        pp.operatorPrecedence(Phrase, [
            (and_tok, 2, pp.opAssoc.LEFT),
            (or_tok, 2, pp.opAssoc.LEFT)
        ])

Expression = pp.OneOrMore(Word ^ Phrase)("Expression")

def test(text):
    output = Expression.parseString(text)
    print output.asXML()

…因为它根本没有产生一个列表:

>>> test("Hello world and bob")
<Expression>
  <Word>Hello</Word>
  <Word>world</Word>
  <Word>and</Word>
  <Word>bob</Word>
</Expression>

如何修改规则定义以便它们处理嵌套列表?

最佳答案
我相信你使用operatorPrecedence的第二种方法是更好的方法.但是,你有几个问题.一个是您的关键字“和”和“或”也是您定义的单词.您可能应该像这样设置它:

and_tok = pyp.Keyword("and")
or_tok = pyp.Keyword("or")
Word = ~(and_tok | or_tok) + pyp.Word(pyp.alphas)("Word")

这将防止“和”和“或”被匹配为单词.

另一个问题是您没有设置operatorPrecedence权限.它的第一个参数应该是“原子”表达式 – 运算符之间可能出现的基本元素. operatorPrecedence自动设置必要的嵌套.通过传入Phrase作为原子,你正在创建一个额外的嵌套级别,这会导致它无法运行.相反,您对operatorPrecedence的第一个参数应该是Word(或者如果要允许多字操作数,则应为pyp.OneOrMore(Word)).

此外,operatorPrecedence将自动处理单个原子的情况,因此您不需要在那里拥有^ Word业务.这意味着您可以省略Phrase,只需将Expression直接作为operatorPrecedence的东西即可.

把它们放在一起就可以得到:

Expression = (
    pyp.operatorPrecedence(pyp.OneOrMore(Word), [
        (and_tok, 2, pyp.opAssoc.LEFT),
        (or_tok, 2, pyp.opAssoc.LEFT)
    ])
)

结果是这样的:

>>> test("Hello and Bob")

<ITEM>
  <ITEM>
    <Word>Hello</Word>
    <AND>and</AND>
    <Word>Bob</Word>
  </ITEM>
</ITEM>

>>> test("TestA and TestB and TestC or TestD")

<ITEM>
  <ITEM>
    <ITEM>
      <Word>TestA</Word>
      <AND>and</AND>
      <Word>TestB</Word>
      <AND>and</AND>
      <Word>TestC</Word>
    </ITEM>
    <OR>or</OR>
    <Word>TestD</Word>
  </ITEM>
</ITEM>

这并不是您想要的形式,因为操作数位于嵌套列表中而不是包装它们,但您应该能够使用parseAction重新配置结构(operatorrPrecedence允许您为每个操作数传递一个).

(另外,您原来想要的测试数据(“TestA和TestB和TestC或TestD”)与您的描述不一致.如果您希望“和”和“或”具有相同的优先级,那么这将包含为(TestA和TestB和TestC)或TestD,就像在上面的例子中一样.如果你想(TestC或TestD)括起来,你需要给予“或”更高的优先权.)

转载注明原文:python – 在pyparsing中嵌套分隔列表而不会导致无限递归? - 代码日志