如何在PyParsing中验证动态定义的语法元素

时间:2013-07-30 12:13:39

标签: python pyparsing

我正在使用PyParsing为一个相当复杂的语法实现一个解析器。 (如果我愿意,可以加入使用!)

语法在某种程度上是“动态的”,允许定义(各种)字母表,而这些字母表又定义了其他定义中允许的元素。举个例子:

alphabet: a b c
lists:
s1 = a b
s2 = b c x

此处,alphabet用于定义lists定义中允许的元素。例如,s1有效,但s2包含无效x

没有这种验证的简单PyParsing解析器可能如下所示:

from pyparsing import Literal, lineEnd, Word, alphanums,\
    OneOrMore, Group, Suppress, dictOf

def fixedToken(literal):
    return Suppress(Literal(literal))

Element = Word(alphanums)

Alphabet = Group(OneOrMore(~lineEnd + Element))
AlphaDef = fixedToken("alphabet:") + Alphabet

ListLine = OneOrMore(~lineEnd + Element)
Lists = dictOf(Word(alphanums) + fixedToken("="), ListLine)

Start = AlphaDef + fixedToken("lists:") + Lists

if __name__ == "__main__":

    data = """
    alphabet: a b c
    lists:
    s1 = a b
    s2 = b c x
    """

    res = Start.parseString(data)
    for k, v in sorted(res.items()):
        print k, "=", v

这将解析&给出输出:

Alphabet= set(['a', 'c', 'b'])
s1 = ['a', 'b']
s2 = ['b', 'c', 'x']

但是,我希望解析器为s2引发ParseException(或类似的),因为它包含无效的x。理想情况下,我希望能够将ListLine的定义说成:OneOrMore(oneOf(Alphabet)) - 但显然,这需要一些动态解释,这只能在Alphabet具有# ... Alphabet = Group(OneOrMore(~lineEnd + Element)) def alphaHold(toks): alphaHold.alpha = set(*toks) print "Alphabet=", alphaHold.alpha Alphabet.addParseAction(alphaHold) AlphaDef = fixedToken("alphabet:") + Alphabet ListLine = OneOrMore(~lineEnd + Element) def lineValidate(toks): unknown = set(toks).difference(alphaHold.alpha) if len(unknown): msg= "Unknown element(s): {}".format(unknown) print msg raise ParseException(msg) ListLine.addParseAction(lineValidate) # ... 之后进行实际上已被解析&组装

我找到的一个解决方案是将解析操作添加到1.记住字母表和2.验证行:

Alphabet= set(['a', 'c', 'b'])
Unknown element(s): set(['x'])
s1 = ['a', 'b']

这几乎提供了所需的输出:

{{1}}

但不幸的是,PyParsing捕获了从解析操作中抛出的异常,因此这种方法在技术性上失败了。在PyParsing中是否有另一种方法可以实现这一点,我可能会错过它?

1 个答案:

答案 0 :(得分:3)

你已经非常接近这项工作了。在许多情况下,pyparsing解析器会根据先前解析的文本动态调整自身。诀窍是使用Forward占位符表达式,然后将所需的值作为解析操作的一部分插入占位符(非常接近现有的位置)。像这样:

Element = Forward()

Alphabet = OneOrMore(~lineEnd + oneOf(list(alphas)))
def alphaHold(toks):
    Element << oneOf(toks.asList())
Alphabet.setParseAction(alphaHold)

从这里开始,我认为其余代码的工作原理相当不错。实际上,您甚至不需要行验证函数,因为pyparsing只会使用此方法将有效元素名称作为元素匹配。

你可能会发现pyparsing的错误报告有点模糊。在一些明智的地方使用' - '而不是'+'可以让事情变得更好。由于pyparsing将ParseExceptions用于表达式匹配/不匹配的所有内部信号,因此它不会自动识别何时进入已定义的表达式,但是在包含的表达式上具有无效匹配。你可以告诉pyparsing使用' - '运算符来检测它,如下所示:

ListDef = listName + '=' - OneOrMore(~lineEnd + Element)

一旦pyparsing获得一个名称和一个'='符号,那么找到的任何无效元素将立即引发ParseSyntaxException,这将停止pyparsing在该点扫描文本,并在该位置报告异常无效元素。