08 识别过程调用 | 《Let’s Build A Simple Interpreter》
本文最后更新于:2021年9月6日
本文的目标是确保当解释器读取一个带有过程调用的程序时,parser 会构造一个 AST,并为过程调用构建一个新的树节点。
Parser
- 新增 AST 节点
class ProcedureCall(AST):
def __init__(self, proc_name, actual_params, token):
self.proc_name = proc_name
self.actual_params = actual_params # a list of AST nodes
self.token = token
- 扩展语法:添加过程调用语句语法 proccall_statement
proccall_statement : ID LPAREN (expr (COMMA expr)*)? RPAREN
下面是几条符合上述语法规则的语句:
Alpha();
Alpha(1);
Alpha(3 + 5, 7);
对应语法的实现如下:
def proccall_statement(self):
"""proccall_statement : ID LPAREN (expr (COMMA expr)*)? RPAREN"""
token = self.current_token
proc_name = self.current_token.value
self.eat(TokenType.ID)
self.eat(TokenType.LPAREN)
actual_params = []
if self.current_token.type != TokenType.RPAREN:
node = self.expr()
actual_params.append(node)
while self.current_token.type == TokenType.COMMA:
self.eat(TokenType.COMMA)
node = self.expr()
actual_params.append(node)
self.eat(TokenType.RPAREN)
node = ProcedureCall(
proc_name=proc_name,
actual_params=actual_params,
token=token,
)
return node
- 扩展语法:将过程调用语句语法 proccall_statement 添加到 statement 中
statement : compound_statement
| proccall_statement
| assignment_statement
| empty
proccall_statement 和 assignment_statement 都是以 ID token 开头的,例如:
foo(); { procedure call }
foo := 5; { assignment }
通过下面的方式可以区分这两者:
if (self.current_token.type == TokenType.ID and
self.lexer.current_char == '('
):
node = self.proccall_statement()
elif self.current_token.type == TokenType.ID:
node = self.assignment_statement()
SemanticAnalyzer
SemanticAnalyzer 的更改仅仅是添加 visit_ProcedureCall 方法:
def visit_ProcedureCall(self, node):
for param_node in node.actual_params:
self.visit(param_node)
Interpreter
对于解释器,由于目前还不用执行过程调用,所以函数没有内容:
def visit_ProcedureCall(self, node):
pass
AST 示例
下面是本文用到的例子:
program Main;
procedure Alpha(a : integer; b : integer);
var x : integer;
begin
x := (a + b) * 2;
end;
begin { Main }
Alpha(3 + 5, 7); { procedure call }
end. { Main }
其对应的 AST 如下:
评论系统采用 utterances ,加载有延迟,请稍等片刻。