[英]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.LEFT
或RIGHT
或者運營商的正確聯系:
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.