16

単純なキー=値クエリ言語を解析しようとしています。私は実際に巨大な怪物パーサーでそれを達成し、それを2回目のパススルーして解析ツリーをクリーンアップしました。私がやりたいのは、ボトムアップでクリーンな解析を行うことです。これには、(key、val)ペアのセットを使用して、冗長なペアを削除するなどが含まれます。以前は機能していましたが、感じません。なぜ構文解析がそのように機能しているのかを完全に理解したように、私は穀物と戦うような多くの回避策などを行いました。

現在、これが私の「簡略化された」パーサーの始まりです。

from pyparsing import *   

bool_act = lambda t: bool(t[0])
int_act  = lambda t: int(t[0])

def keyval_act(instring, loc, tokens):
    return set([(tokens.k, tokens.v)])

def keyin_act(instring, loc, tokens):
    return set([(tokens.k, set(tokens.vs))])

string = (
      Word(alphas + '_', alphanums + '_')
    | quotedString.setParseAction( removeQuotes )
    )
boolean = (
      CaselessLiteral('true')
    | CaselessLiteral('false')
    )
integer = Word(nums).setParseAction( int_act )
value = (
      boolean.setParseAction(bool_act)
    | integer
    | string
    )
keyval = (string('k') + Suppress('=') + value('v')
          ).setParseAction(keyval_act)
keyin = (
    string('k') + Suppress(CaselessLiteral('in')) +
    nestedExpr('{','}', content = delimitedList(value)('vs'))
    ).setParseAction(keyin_act)

grammar = keyin + stringEnd | keyval + stringEnd

現在、「文法」非終端記号は単なるスタブです。最終的には、次のような検索を解析できるように、キーにネスト可能な接続詞と論理和を追加します。

a = 1, b = 2 , c in {1,2,3} | d = 4, ( e = 5 | e = 2, (f = 3, f = 4))

しかし今のところ、pyparsingがsetParseAction関数を呼び出す方法を理解するのに苦労しています。渡される引数の数に関しては魔法があることは知っていますが、関数に引数がまったく渡されていないというエラーが発生します。だから現在、私がそうするなら:

grammar.parseString('hi in {1,2,3}')

このエラーが発生します:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/site-packages/pyparsing.py", line 1021, in parseString
    loc, tokens = self._parse( instring, 0 )
  File "/usr/lib/python2.6/site-packages/pyparsing.py", line 894, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/usr/lib/python2.6/site-packages/pyparsing.py", line 2478, in parseImpl
    ret = e._parse( instring, loc, doActions )
  File "/usr/lib/python2.6/site-packages/pyparsing.py", line 894, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/usr/lib/python2.6/site-packages/pyparsing.py", line 2351, in parseImpl
    loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False )
  File "/usr/lib/python2.6/site-packages/pyparsing.py", line 921, in _parseNoCache
    tokens = fn( instring, tokensStart, retTokens )
  File "/usr/lib/python2.6/site-packages/pyparsing.py", line 675, in wrapper
    return func(*args[limit[0]:])
TypeError: keyin_act() takes exactly 3 arguments (0 given)

トレースバックからわかるように、私はpython2.6を使用しており、1.5.6をpyparsingしています。

関数が適切な数の引数を取得していない理由について、誰かが私に洞察を与えることができますか?

4

2 に答える 2

21

さて、の最新バージョンsetParseActionはいくつかの余分な魔法を行いますが、残念ながら開発の単純さを犠牲にして。setParseActionの引数検出ロジックは、3から始まり、0まで下がる正しい数の引数で呼び出されるまで、解析アクションで例外を発生させることに依存するようになりました。その後、例外をあきらめて発生させます。鋸。

この場合を除いて、解析アクションから発生する例外は、引数リストの不一致によるものではなく、コードの実際のエラーでした。これをよりよく理解するには、一般的なtry-exceptを解析アクションに挿入します。

def keyin_act(instring, loc, tokens): 
    try:
        return set([(tokens.k, set(tokens.vs[0]))]) 
    except Exception as e:
        print e

そして、あなたは得る:

unhashable type: 'set'

実際、リターンセットを作成しているリストの2番目の要素は、それ自体がセットであり、変更可能なコンテナーであるため、セットに含めるためにハッシュすることはできません。代わりにfrozensetを使用するようにこれを変更すると、次のようになります。

[set([('hi', frozenset([]))])]

なぜフリーズセットが空なのですか?結果名「vs」の場所を次のように変更することをお勧めします。

nestedExpr('{','}', content = delimitedList(value))('vs') 

そして今、「hiin{1,2,3}」を解析することによって返される解析結果は次のとおりです。

[set([('hi', frozenset([([1, 2, 3], {})]))])]

これは混乱のようなものです。解析アクションの先頭にこの行をドロップすると、さまざまな名前の結果に実際に何が含まれているかがわかります。

print tokens.dump()

我々が得る:

['hi', [1, 2, 3]]
- k: hi
- vs: [[1, 2, 3]]

したがって、「vs」は実際にはリストを含むリストを指します。tokens.vs[0]したがって、おそらく、からではなく、からセットを構築する必要がありtokens.vsます。これで、解析された結果は次のようになります。

[set([('hi', frozenset([1, 2, 3]))])]

文法に関するその他のヒント:

  • CaselessLiteralの代わりに、CaselessKeywordを使用してみてください。キーワードは、文法のキーワード「in」と「inside」の先頭の「in」を本質的に間違えないようにするため、文法キーワードに適しています。

  • 解析アクションからセットを返す方向がわからない-キーと値のペアの場合、トークンの順序が保持されるため、タプルの方がおそらく優れています。プログラムの解析後のフェーズで、キーと値のセットを作成します。

  • その他の文法デバッグツールについてsetDebugは、traceParseActionデコレータを確認してください。

于 2012-04-17T03:55:11.850 に答える
5

Paulは、根本的な問題が何であるかをすでに説明していTypeErrorます。解析アクションによって発生したものは、解析アクションが期待する引数の数を計算するpyparsingの自動魔法の方法を混乱させます。

この種の混乱を避けるために私が使用するものは次のとおりTypeErrorです。関数がより少ない引数で再度呼び出された場合に、装飾された関数によってスローされたものを再レイズするデコレータ:

import functools
import inspect
import sys

def parse_action(f):
    """
    Decorator for pyparsing parse actions to ease debugging.

    pyparsing uses trial & error to deduce the number of arguments a parse
    action accepts. Unfortunately any ``TypeError`` raised by a parse action
    confuses that mechanism.

    This decorator replaces the trial & error mechanism with one based on
    reflection. If the decorated function itself raises a ``TypeError`` then
    that exception is re-raised if the wrapper is called with less arguments
    than required. This makes sure that the actual ``TypeError`` bubbles up
    from the call to the parse action (instead of the one caused by pyparsing's
    trial & error).
    """
    num_args = len(inspect.getargspec(f).args)
    if num_args > 3:
        raise ValueError('Input function must take at most 3 parameters.')

    @functools.wraps(f)
    def action(*args):
        if len(args) < num_args:
            if action.exc_info:
                raise action.exc_info[0], action.exc_info[1], action.exc_info[2]
        action.exc_info = None
        try:
            return f(*args[:-(num_args + 1):-1])
        except TypeError as e:
            action.exc_info = sys.exc_info()
            raise

    action.exc_info = None
    return action

使用方法は次のとおりです。

from pyparsing import Literal

@parse_action
def my_parse_action(tokens):
    raise TypeError('Ooops')

x = Literal('x').setParseAction(my_parse_action)
x.parseString('x')

これはあなたに与えます:

Traceback (most recent call last):
  File "test.py", line 49, in <module>
    x.parseString('x')
  File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 1101, in parseString
    loc, tokens = self._parse( instring, 0 )
  File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 1001, in _parseNoCache
    tokens = fn( instring, tokensStart, retTokens )
  File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 765, in wrapper
    ret = func(*args[limit[0]:])
  File "test.py", line 33, in action
    return f(*args[:num_args])
  File "test.py", line 46, in my_parse_action
    raise TypeError('Ooops')
TypeError: Ooops

@parse_actionこれを、装飾なしで取得するトレースバックと比較してください。

Traceback (most recent call last):
  File "test.py", line 49, in <module>
    x.parseString('x')
  File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 1101, in parseString
    loc, tokens = self._parse( instring, 0 )
  File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 1001, in _parseNoCache
    tokens = fn( instring, tokensStart, retTokens )
  File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 765, in wrapper
    ret = func(*args[limit[0]:])
TypeError: my_parse_action() takes exactly 1 argument (0 given)
于 2014-07-14T18:27:55.340 に答える