3

PyParsing を使用して、かなり複雑な文法のパーサーを実装しています。(付け加えるとすれば、これは使用するのが本当に楽しいことです!)

文法は、(さまざまな) アルファベットの定義を可能にするという点で、いくぶん「動的」であり、他の定義で許可される要素を定義します。例として:

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

ここで、alphabetは定義で許可される要素を定義することを意味しますlists。たとえば、有効ですが、無効なs1s2含まれています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無効なx. 理想的には、 の定義を次のように定義できるようにListLineOneOrMore(oneOf(Alphabet))たいと考えていAlphabetます。

私が見つけた 1 つの解決策は、1. アルファベットを記憶し、2. 行を検証するために解析アクションを追加することでした。

# ...
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)
# ...

これにより、ほぼ目的の出力が得られます。

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

しかし残念なことに、PyParsing は解析アクションからスローされた例外をキャッチするため、このアプローチは技術的に失敗します。私が見逃したかもしれないPyParsing内でこれを達成する別の方法はありますか?

4

1 に答える 1

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 によるテキストのスキャンを停止し、無効な要素の場所で例外を報告します。

于 2013-07-30T13:02:04.187 に答える