本系列是《Let’s Build A Simple Interpreter》的阅读笔记。
本文的目标是确保当解释器读取一个带有过程调用的程序时,parser 会构造一个 AST,并为过程调用构建一个新的树节点。
Parser
- 新增 AST 节点
1 2 3 4 5
| class ProcedureCall(AST): def __init__(self, proc_name, actual_params, token): self.proc_name = proc_name self.actual_params = actual_params self.token = token
|
- 扩展语法:添加过程调用语句语法 proccall_statement
1
| proccall_statement : ID LPAREN (expr (COMMA expr)*)? RPAREN
|
下面是几条符合上述语法规则的语句:
1 2 3
| Alpha(); Alpha(1); Alpha(3 + 5, 7);
|
对应语法的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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 中
1 2 3 4
| statement : compound_statement | proccall_statement | assignment_statement | empty
|
proccall_statement 和 assignment_statement 都是以 ID token 开头的,例如:
通过下面的方式可以区分这两者:
1 2 3 4 5 6
| 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 方法:
1 2 3
| def visit_ProcedureCall(self, node): for param_node in node.actual_params: self.visit(param_node)
|
Interpreter
对于解释器,由于目前还不用执行过程调用,所以函数没有内容:
1 2
| def visit_ProcedureCall(self, node): pass
|
AST 示例
下面是本文用到的例子:
1 2 3 4 5 6 7 8 9 10
| program Main; procedure Alpha(a : integer; b : integer); var x : integer; begin x := (a + b) * 2; end;
begin Alpha(3 + 5, 7); end.
|
其对应的 AST 如下: