Python 如何在pyparsing中解析节点和节点关系?

Python 如何在pyparsing中解析节点和节点关系?,python,parsing,nodes,pyparsing,Python,Parsing,Nodes,Pyparsing,我已经构建了一个原始解析器,但我真的希望在pyparsing中实现这一点 我想分析两种类型的字符串。 只解析节点和第二个节点关系的节点 verb node1, node2, ... 及 可以指定一个或多个可以引用的节点 此外,通过添加^ verb node1, node2 ^ node3, node4 您可能还希望通过使用->,来指示节点关系,此格式的概念性BNF如下所示: node :: word composed of alphas, digits, '_' verb :: one o

我已经构建了一个原始解析器,但我真的希望在pyparsing中实现这一点

我想分析两种类型的字符串。 只解析节点和第二个节点关系的节点

verb node1, node2, ... 

可以指定一个或多个可以引用的节点 此外,通过添加
^

verb node1, node2 ^ node3, node4

您可能还希望通过使用
->
来指示节点关系,此格式的概念性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]...
最后,我们定义了整体表达式,我将其解释为某种命令。节点表达式的逗号分隔列表可以直接实现为
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)
此时,您只需解析命令,然后根据
谓词
,分派到执行该谓词的适当方法

但让我建议一种结构,我发现它有助于使用Python对象捕获这种逻辑。定义一个简单的命令类层次结构,这些命令在抽象方法
docomand
中实现各种动词函数:

# 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)
但您也可以将其作为解析时间回调构建到解析器中,以便解析完成后,您不仅可以获得字符串和子列表列表,还可以通过调用其
docomand
方法获得一个准备好“执行”的对象。为此,只需将
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)
由于我们没有在子类上实际实现
docomand
,因此我们得到的只是默认的基类行为,即只回显解析的动词和节点列表:

GO :: ['node1', 'node2']
TURN :: [['node1', '->', 'node2', '->', 'node3']]
GO :: ['node1', ['node2', '^', 'node3'], 'node4']
FOLLOW :: [['node1', '->', 'node2', '<->', 'node3']]
GO :: ['node5', ['node1', '->', ['node2', '^', 'node4'], '<->', 'node3'], 'node6']
这将构建一个全新的ParseResults来替换原始的链接结果。注意每个
lastexpr op nextexpr
如何保存为自己的子组,然后
nextexpr
被复制到
lastexpr
,然后循环获取下一个op nextexpr对

要将此重新格式附加到解析器中,请将其作为该层次结构级别的第四个元素添加到
infixNotation

nodeExpr = infixNotation(nodeRef,
    [
    ('^', 2, opAssoc.LEFT),
    (binop, 2, opAssoc.LEFT, expandChainedExpr),
    ])
现在输出:

FOLLOW node1->node2<->node3
跟随节点1->节点2节点3
扩展到:

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

查看您已经尝试过的内容会很有帮助。请用最小的形式向我们展示你的“原始语法分析器”,我们可以从这里帮助解决问题。我用标准的语法结构解决了第一个解析问题,比如动词+ OnORMOR(节点+可选)……第二个问题真的很有趣,因为你实际上想提取对。考虑A->B-> C应该产生(a,‘->,b),(b,'->),(b,'->,c)。解析时,您希望将之前解析的节点包含在组中。首先,感谢您的回答Paul。Pyparsing真是太棒了!您已经回答了我的大多数问题。我有一个悬而未决的问题。如果您正在解析以下内容的事件,请单击node1->node2node3,理想情况下,您希望得到如下列表[('node1','->',node2),('node2','node3')]。您始终可以重新分析输出,但我想知道pyparsing是否可以“回顾”它所分析的上一个节点,以使用它来构造下一个已分析的组。请参阅答案后面的编辑。我没有真正的解析器定义解决方案,但使用解析操作将已分析的标记从
a ob b op c
重新构造为
a op b
b op c
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)
GO :: ['node1', 'node2']
TURN :: [['node1', '->', 'node2', '->', 'node3']]
GO :: ['node1', ['node2', '^', 'node3'], 'node4']
FOLLOW :: [['node1', '->', 'node2', '<->', 'node3']]
GO :: ['node5', ['node1', '->', ['node2', '^', 'node4'], '<->', 'node3'], 'node6']
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
nodeExpr = infixNotation(nodeRef,
    [
    ('^', 2, opAssoc.LEFT),
    (binop, 2, opAssoc.LEFT, expandChainedExpr),
    ])
FOLLOW node1->node2<->node3
('FOLLOW', '::', [['node1', '->', 'node2'], ['node2', '<->', 'node3']])