08 识别过程调用 | 《Let’s Build A Simple Interpreter》

本文最后更新于:2021年9月6日

本文的目标是确保当解释器读取一个带有过程调用的程序时,parser 会构造一个 AST,并为过程调用构建一个新的树节点。

Parser

  1. 新增 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
  1. 扩展语法:添加过程调用语句语法 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
  1. 扩展语法:将过程调用语句语法 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 如下: