在PLY Python 3中循环解析步骤

时间:2018-12-31 16:46:50

标签: python-3.x parsing ply

我目前正在使用python 3解析我正在编写的编程语言。我想知道如何进行解析规则循环。

可能不清楚,所以这里是一个例子。

我有代码:

def c_parse():
    def p_program(p):
        "program : actions"
        p[0] = ("PROGRAM", p[1])

    def p_actions(p):
        """actions : action
                   | actions action"""
        if len(p) == 3:
            p[0] = ("ACTIONS", p[1], p[2])
        elif len(p) == 2:
            p[0] = ("ACTION", p[1])

    def p_action(p):
        """action : function_call ';'
                  | variable_dec ';'
                  | if_statement ';'"""
        p[0] = ("ACTION_STATEMENT", p[1])

    ...

输入:

x = "HELLO WORLD";
print(x);
print(x);

这将输出此AST:

('PROGRAM', ('ACTIONS', ('ACTIONS', ('ACTION', ('ACTION_STATEMENT', 
('VARIABLE_DEC', ... ))), 
('ACTION_STATEMENT', ('FUNCTION_CALL', ... ))), ('ACTION_STATEMENT', 
('FUNCTION_CALL', ... ))))

请注意,ACTIONS和ACTION_STATEMENT非常混乱。发生这种情况是因为p_actions()函数中定义了递归规则。有什么办法可以让我得到一个像这样的干净整洁的AST:

(PROGRAM, (ACTION, (VARIABLE_DEC, ... )), (ACTION, (FUNCTION_CALL, ... )),
(ACTION, (FUNCTION_CALL, ...)))

换句话说,我可以使p_actions()函数能够解析无数个ACTION节点而无需使用递归吗?这可能吗?

顺便说一句,如果有问题,我正在使用macOS。

1 个答案:

答案 0 :(得分:1)

如果使用的是上下文无关的语法,则不使用递归就无法解析无限长度的输入,因为递归是您在上下文无关的语法中表达无限重复的方式。这会影响形式语法树,但是绝对没有理由为什么抽象语法树(AST)需要保留解析的每个细节。

AST之所以称为摘要,恰恰是因为AST提取了一些语法细节,这些细节对于语义分析不是很有用。您可以按照自己喜欢的任何方式自由创建AST。没有任意的规则。 (有一种启发式方法:AST应该完全保留解析树的那些对您有用的功能,而不再需要其他功能。)

从AST中移除单元产品尤为常见。 (单位产品是其右侧仅由一个非终结符组成的产品,例如actions: action),如果它们没有添加任何有用的信息。有时,即使严格说来不是单位规则,在右侧只有一个非终端的产品也会从AST中删除。这将取决于产品是否具有语义意义。例如,expression: '(' expression ')'可能会被省略,尽管expression: '-' expression不会。

用Ply表示,省略生产只是简单地从右侧传递值。例如,您可能有:

def unit_rule(p):
  """actions : action
     program : actions
  """
  p[0] = p[1]   # Pass through the non-terminal's value

def parens(p):
  """expr : LPAREN expr RPAREN"""
  p[0] = p[2]   # Pass through the non-terminal's value

您还不仅限于创建忠实地模仿语法结构的语法节点。如果您希望列表在AST中表示为列表,那很好。 Python的append操作对此非常有用。

因此获得您似乎想要的AST的一种可能方法是:

start = 'program'

def p_actions(p):
    """actions : actions action"""
    p[1].append(p[2])
    p[0] = p[1]       

def p_actions1(p):
    """actions : action"""
    p[0] = ["ACTIONS", p[1]]

def p_unit(p):
    """program : actions"
       action  : function_call ';'
               | variable_dec ';'
               | if_statement ';'
    """
    p[0] = p[1]

关于上述代码的一些注释:

  1. 我没有看到ACTION节点的意义,所以我只是在最后一条规则中通过了语句本身。由于actions仅包含ACTION,因此将列表中的每个元素标记为ACTION似乎是多余的。但是您可以根据需要输入ACTION。)

  2. 在上面的代码中,ACTIONS节点是一个列表,而不是一个元组; p_actions函数在每次添加新项目时都不会故意创建新列表。通常这很好,并节省了许多元组创建和垃圾回收。它假定传递的值未在其他任何地方使用,这在这里确实是这种情况,但可能并不总是正确的。但是,如果您真的想要元组,那不是问题:

    def p_actions(p):
        """actions : actions action"""
        p[0] = p[1] + (p[2],)
    
  3. 请注意,没有必要将非终结符的所有产生都放在相同的解析函数中。如果产品的动作相同,则可以将其分为功能。总体而言,如果您发现自己想找出哪个生产导致触发了动作功能(例如,if len(p) == 3),那么您可能要考虑将生产分成两个不同的功能,每个功能采取无条件的行动。