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

2024-05-08 08:51:13 发布

您现在位置:Python中文网/ 问答频道 /正文

我已经构建了一个原始的解析器,但是我真的希望在pyparsing中使用它。在

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

verb node1, node2, ... 

以及

^{pr2}$

可以指定一个或多个可以引用的节点 另外,您可以通过添加^来指示一个节点在另一个节点内

verb node1, node2 ^ node3, node4

您还可以使用-><-<->指示符来指示节点关系。在

verb node1->node2<->node3

同样,您可以使用^来指示一个节点在另一个节点内

verb node1->node2^node4<->node3

Tags: 字符串程序解析器类型节点关系pyparsingverb
1条回答
网友
1楼 · 发布于 2024-05-08 08:51:13

此格式的概念性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:

^{pr2}$

下一部分最容易使用pyparsing的infixNotation方法(以前称为operatorPrecedence)实现。infixNotation允许我们定义操作的层次结构,并将根据层次结构定义的优先级对解析的输出进行分组。我假设您的'^'“is inside”运算符应该在二进制'->'等运算符之前求值。infixNotation也允许在括号中嵌套,但是没有一个示例显示这是绝对需要的。定义infixNotation的方法是:指定基本操作数类型,后跟3个元组的列表,每个元组显示运算符,一元运算符、二元运算符或三元运算符的值为1、2或3,运算符的左或右关联性为常量opAssoc.LEFT或{}:

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']

(这段代码是用python3,pyparsing 2.0.0运行的。它还将与python2一起运行,pyparsing 1.5.7)

编辑

要将链接表达式a op b op c转换为[a,op,b], [b, op, c],请使用parse操作将解析后的[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']])

相关问题 更多 >