3

生のパーサーを作成しましたが、これをpyparsingで機能させたいと思っています。

解析したい文字列は2種類あります。ノードと2番目のノードの関係のみを解析するもの

verb node1, node2, ... 

verb node1->node2->node3

1つ以上のノードを指定できます。さらに引用することができます。追加することにより、ノードが別のノード内にあることを示すことができます。^

verb node1, node2 ^ node3, node4

->、、<-または<->インジケーターを使用してノードの関係を示すこともできます。

verb node1->node2<->node3

ここでも、ノードを使用してノードが別のノードの内部にあることを示すことができます。^

verb node1->node2^node4<->node3
4

1 に答える 1

2

この形式の概念的な BNF は次のようになります。

node :: word composed of alphas, digits, '_'
verb :: one of several defined keywords
binop :: '->' | '<-' | '<->'
nodeFactor :: node '^' node | node
nodeExpr :: nodeFactor op nodeFactor
nodeCommand :: verb nodeExpr [',' nodeExpr]...

これは、ほぼステップごとに pyparsing にマップされます。

from pyparsing import (Word,alphas,alphanums,Keyword,
    infixNotation,opAssoc,oneOf,delimitedList)

nodeRef = Word(alphas,alphanums+'_')
GO, TURN, FOLLOW = map(Keyword, "GO TURN FOLLOW".split())
verb = GO | TURN | FOLLOW
binop = oneOf('-> <- <->')

次の部分は、pyparsing のinfixNotation方法 (以前は として知られていましたoperatorPrecedence) を使用して最も簡単に実装できます。infixNotation操作の階層を定義することができ、階層によって定義された優先順位に従って解析された出力をグループ化します。'^'「内部にある」演算子は、バイナリ'->'などの演算子の前に評価する必要があると想定しています。infixNotation括弧内のネストも許可されますが、これが絶対に必要であると示す例はありません。を定義するにはinfixNotation、ベース オペランドの型を指定し、続いて 3 つのタプルのリストを指定します。各タプルには、演算子、単項演算子、2 項演算子、または 3 項演算子の値 1、2 または 3、および左結合または右結合の定数opAssoc.LEFTまたはが示されます。RIGHTオペレーター:

nodeExpr = infixNotation(nodeRef,
    [
    ('^', 2, opAssoc.LEFT),
    (binop, 2, opAssoc.LEFT),
    ])

最後に、ある種のコマンドとして解釈した全体的な式を定義します。ノード式のコンマ区切りのリストは、次のように直接実装できますnodeExpr + ZeroOrMore(Suppress(',') + nodeExpr)(解析された出力からコンマを抑制します - コンマは解析時に役立ちますが、後でスキップする必要があります)。しかし、これは非常に頻繁に発生するため、pyparsing はメソッドを提供しますdelimitedList:

nodeCommand = verb('verb') + delimitedList(nodeExpr)('nodes')

「verb」および「nodes」という名前により、それぞれの式で解析された結果がそれらの名前に関連付けられます。これにより、解析が完了した後に解析されたデータを簡単に操作できるようになります。

パーサーをテストするには:

tests = """\
    GO node1,node2
    TURN node1->node2->node3
    GO node1,node2^node3,node4
    FOLLOW node1->node2<->node3
    GO node5,node1->node2^node4<->node3,node6
    """.splitlines()
for test in tests:
    test = test.strip()
    if not test:
        continue
    print (test)
    try:
        result = nodeCommand.parseString(test, parseAll=True)
        print (result.dump())
    except ParseException as pe:
        print ("Failed:", test)
        print (pe)

このdump()メソッドは、解析されたトークンをネストされたリストとして出力し、各結果の名前とそれに添付された値をリストします。

GO node1,node2
['GO', 'node1', 'node2']
- nodes: ['node1', 'node2']
- verb: GO
TURN node1->node2->node3
['TURN', ['node1', '->', 'node2', '->', 'node3']]
- nodes: [['node1', '->', 'node2', '->', 'node3']]
- verb: TURN
GO node1,node2^node3,node4
['GO', 'node1', ['node2', '^', 'node3'], 'node4']
- nodes: ['node1', ['node2', '^', 'node3'], 'node4']
- verb: GO
FOLLOW node1->node2<->node3
['FOLLOW', ['node1', '->', 'node2', '<->', 'node3']]
- nodes: [['node1', '->', 'node2', '<->', 'node3']]
- verb: FOLLOW
GO node5,node1->node2^node4<->node3,node6
['GO', 'node5', ['node1', '->', ['node2', '^', 'node4'], '<->', 'node3'], 'node6']
- nodes: ['node5', ['node1', '->', ['node2', '^', 'node4'], '<->', 'node3'], 'node6']
- verb: GO

この時点で、コマンドを解析してから、 に基づいて、verbその動詞を実行する適切なメソッドにディスパッチできます。

しかし、Python オブジェクトを使用してこのロジックをキャプチャするのに役立つことがわかった構造を提案させてください。抽象メソッドでさまざまな動詞関数を実装するコマンドの単純なクラス階層を定義しますdoCommand

# base class
class Command(object):
    def __init__(self, tokens):
        self.cmd = tokens.verb
        self.nodeExprs = tokens.nodes

    def doCommand(self):
        """
        Execute command logic, using self.cmd and self.nodeExprs.
        To be overridden in sub classes.
        """
        print (self.cmd, '::', self.nodeExprs.asList())

# these should implement doCommand, but not needed for this example
class GoCommand(Command): pass
class TurnCommand(Command): pass
class FollowCommand(Command): pass

このメソッドは、解析された結果を適切なコマンド クラスのインスタンスに変換します。

verbClassMap = {
    'GO' : GoCommand,
    'TURN' : TurnCommand,
    'FOLLOW' : FollowCommand,
    }
def tokensToCommand(tokens):
    cls = verbClassMap[tokens.verb]
    return cls(tokens)

ただし、これを解析時のコールバックとしてパーサーに組み込むこともできます。これにより、解析が完了すると、文字列とサブリストのリストだけでなく、doCommandメソッドを呼び出して「実行」できるオブジェクトを取得できます。これを行うには、式tokensToCommand全体に解析アクションとしてアタッチします。nodeCommand

nodeCommand.setParseAction(tokensToCommand)

ここで、テスト コードを少し変更します。

for test in tests:
    test = test.strip()
    if not test:
        continue
    try:
        result = nodeCommand.parseString(test, parseAll=True)
        result[0].doCommand()
    except ParseException as pe:
        print ("Failed:", test)
        print (pe)

実際doCommandにはサブクラスに実装していないため、得られるのはデフォルトの基本クラスの動作だけです。これは、解析された動詞とノード リストをエコー バックするだけです。

GO :: ['node1', 'node2']
TURN :: [['node1', '->', 'node2', '->', 'node3']]
GO :: ['node1', ['node2', '^', 'node3'], 'node4']
FOLLOW :: [['node1', '->', 'node2', '<->', 'node3']]
GO :: ['node5', ['node1', '->', ['node2', '^', 'node4'], '<->', 'node3'], 'node6']

(このコードは Python 3、pyparsing 2.0.0 で実行されました。Python 2、pyparsing 1.5.7 でも実行されます。)

編集

連鎖式を に取得するa op b op c[a,op,b], [b, op, c]は、解析アクションを使用して [a,op,b,op,c] の結果をペアワイズ式に解析して再構築します。このinfixNotationメソッドを使用すると、演算子階層のレベルにアタッチする解析アクションを定義できます。

連鎖式の結果を再構築する方法は次のようになります。

def expandChainedExpr(tokens):
    ret = ParseResults([])
    tokeniter = iter(tokens[0])
    lastexpr = next(tokeniter)
    for op,nextexpr in zip(tokeniter,tokeniter):
        ret += ParseResults([[lastexpr, op, nextexpr]])
        lastexpr = nextexpr
    return ret

これにより、まったく新しい ParseResults が構築され、元の連鎖結果が置き換えられます。それぞれlastexpr op nextexprが独自のサブグループとして保存され、次にnextexprにコピーされlastexpr、ループして次の op-nextexpr ペアを取得する方法に注意してください。

このリフォーマッタをパーサーにアタッチするには、その階層のレベルの 4 番目の要素として次のように追加しますinfixNotation

nodeExpr = infixNotation(nodeRef,
    [
    ('^', 2, opAssoc.LEFT),
    (binop, 2, opAssoc.LEFT, expandChainedExpr),
    ])

今の出力:

FOLLOW node1->node2<->node3

次のように展開されます。

('FOLLOW', '::', [['node1', '->', 'node2'], ['node2', '<->', 'node3']])
于 2013-03-02T12:40:59.213 に答える