我目前正在使用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。
答案 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]
关于上述代码的一些注释:
我没有看到ACTION
节点的意义,所以我只是在最后一条规则中通过了语句本身。由于actions
仅包含ACTION
,因此将列表中的每个元素标记为ACTION
似乎是多余的。但是您可以根据需要输入ACTION
。)
在上面的代码中,ACTIONS
节点是一个列表,而不是一个元组; p_actions
函数在每次添加新项目时都不会故意创建新列表。通常这很好,并节省了许多元组创建和垃圾回收。它假定传递的值未在其他任何地方使用,这在这里确实是这种情况,但可能并不总是正确的。但是,如果您真的想要元组,那不是问题:
def p_actions(p):
"""actions : actions action"""
p[0] = p[1] + (p[2],)
请注意,没有必要将非终结符的所有产生都放在相同的解析函数中。如果产品的动作相同,则可以将其分为功能。总体而言,如果您发现自己想找出哪个生产导致触发了动作功能(例如,if len(p) == 3
),那么您可能要考虑将生产分成两个不同的功能,每个功能采取无条件的行动。