簡體   English   中英

如何解析pyparsing中的節點和節點關系?

[英]How do you parse node and node relationships in pyparsing?

我已經構建了一個原始解析器,但我真的很想讓它在pyparsing中工作。

我想解析兩種類型的字符串。 一個解析節點和第二個節點關系

verb node1, node2, ... 

verb node1->node2->node3

您可以指定一個或多個節點,可以另外引用,您可以通過添加^來指示節點位於另一個節點內

verb node1, node2 ^ node3, node4

您可能還希望使用-><-<->指示符來指示節點關系。

verb node1->node2<->node3

您可以再次使用^指示節點在另一個節點內

verb node1->node2^node4<->node3

這種格式的概念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元組列表,每個元組顯示運算符,一元,二元或三元運算符的值為1,2或3,左側為常量opAssoc.LEFTRIGHT或者運營商的正確聯系:

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

最后,我們定義了整體表達式,我將其解釋為某種命令。 逗號分隔的節點表達式列表可以直接實現為nodeExpr + ZeroOrMore(Suppress(',') + nodeExpr) (我們從解析的輸出中抑制逗號 - 它們在分析時很有用,但之后我們只需要跳過在他們身上)。 但是,這出現如此頻繁,pyparsing提供的方法delimitedList

nodeCommand = verb('verb') + delimitedList(nodeExpr)('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對。

要將此重新格式化程序附加到解析器中,請將其添加為infixNotation中該層次結構級別的第四個元素:

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

現在輸出:

FOLLOW node1->node2<->node3

擴展到:

('FOLLOW', '::', [['node1', '->', 'node2'], ['node2', '<->', 'node3']])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM