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

时间:2012-08-20 21:48:55

标签: python pyparsing operator-precedence

我在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_PhraseOr_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>

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

1 个答案:

答案 0 :(得分:3)

我相信你使用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允许您传入一个每个操作数)。

(另外,test("TestA and TestB and TestC or TestD")的原始所需数据与您的描述不一致。如果您希望“和”和“或”具有相同的优先级,那么这将包含为(TestA and TestB and TestC) or TestD,如它在上面的例子中。如果你想要(TestC or TestD)括起来,你需要给“或”更高的优先权。)