Класс NodeVisitor для анализатора PEG в Python - PullRequest
3 голосов
/ 28 марта 2019

Представьте себе следующие типы строк:

if ((a1 and b) or (a2 and c)) or (c and d) or (e and f)

Теперь я хотел бы получить выражения в скобках, поэтому я написал парсер PEG со следующей грамматикой:

from parsimonious.grammar import Grammar

grammar = Grammar(
    r"""
    program     = if expr+
    expr        = term (operator term)*
    term        = (factor operator factor) / factor
    factor      = (lpar word operator word rpar) / (lpar expr rpar)

    if          = "if" ws
    and         = "and"
    or          = "or"
    operator    = ws? (and / or) ws?

    word        = ~"\w+"
    lpar        = "("
    rpar        = ")"

    ws          = ~"\s*"
    """)

, который отлично разбирается с

tree = grammar.parse(string)

Теперь возникает вопрос: как написать класс NodeVisitor для этого дерева, чтобы получить только факторы? Моя проблема здесь - вторая ветвь, которая может быть глубоко вложенной.


Я пробовал с
def walk(node, level = 0):
    if node.expr.name == "factor":
        print(level * "-", node.text)

    for child in node.children:
        walk(child, level + 1)

walk(tree)

но безрезультатно, правда (факторы дублируются в двух экземплярах).
Примечание: Этот вопрос основан на еще одном в StackOverflow.

Ответы [ 2 ]

2 голосов
/ 01 апреля 2019

Как бы я поступил так, чтобы получить ((a1 и b) или (a2 и c)), (c и d) и (e и f) как три части?

Вы можете создать посетителя, который «слушает», когда узел в дереве разбора равен (, в котором переменная глубины увеличивается, а когда встречается ), переменная глубины уменьшается. Затем в вызываемом методе, который соответствует выражению в скобках, вы проверяете глубину, прежде чем добавить ее в свой список выражений для возврата от посетителя.

Вот краткий пример:

from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor

grammar = Grammar(
    r"""
    program     = if expr+
    expr        = term (operator term)*
    term        = (lpar expr rpar) / word

    if          = "if" ws
    and         = "and"
    or          = "or"
    operator    = ws? (and / or) ws?

    word        = ~"\w+"
    lpar        = "("
    rpar        = ")"

    ws          = ~"\s*"
    """)


class ParExprVisitor(NodeVisitor):

    def __init__(self):
        self.depth = 0
        self.par_expr = []

    def visit_term(self, node, visited_children):
        if self.depth == 0:
            self.par_expr.append(node.text)

    def visit_lpar(self, node, visited_children):
        self.depth += 1

    def visit_rpar(self, node, visited_children):
        self.depth -= 1

    def generic_visit(self, node, visited_children):
        return self.par_expr


tree = grammar.parse("if ((a1 and b) or (a2 and c)) or (c and d) or (e and f)")
visitor = ParExprVisitor()

for expr in visitor.visit(tree):
    print(expr)

который печатает:

((a1 and b) or (a2 and c))
(c and d)
(e and f)
1 голос
/ 31 марта 2019

Если вы хотите только вернуть каждый внешний фактор, return рано и не спускайтесь в его потомки.

def walk(node, level = 0):
    if node.expr.name == "factor":
        print(level * "-", node.text)
        return
    for child in node.children:
        walk(child, level + 1)

Выход:

----- ((a1 and b) or (a2 and c))
----- (c and d)
------ (e and f)
...