10 执行过程调用 | 《Let’s Build A Simple Interpreter》笔记

本系列是《Let’s Build A Simple Interpreter》的阅读笔记。

本文将解读执行过程调用的代码。

创建活动记录

1
2
3
class ARType(Enum):
PROGRAM = 'PROGRAM'
PROCEDURE = 'PROCEDURE'
1
2
3
4
5
6
7
8
def visit_ProcedureCall(self, node):
proc_name = node.proc_name

ar = ActivationRecord(
name=proc_name,
type=ARType.PROCEDURE,
nesting_level=2,
)

在活动记录中保存过程实参

以下是描述解释器在活动记录中保存过程参数所需采取的步骤:

  • 获取过程的形参列表
  • 获取过程的实参列表
  • 对于每个形参,获取对应的实参,以形参名称为键,实参求值后作为值保存在过程的活动记录中

过程调用的形参被保存在 ProcedureSymbol 中:

1
2
3
4
5
6
7
8
9
10
11
class Symbol:
def __init__(self, name, type=None):
self.name = name
self.type = type


class ProcedureSymbol(Symbol):
def __init__(self, name, formal_params=None):
super().__init__(name)
# a list of VarSymbol objects
self.formal_params = [] if formal_params is None else formal_params

代码令 ProcedureCall 节点持有 ProcedureSymbol 数据:

1
2
3
4
5
6
7
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
# a reference to procedure declaration symbol
self.proc_symbol = None

这样在语义分析的时候就可以获取到过程调用节点的形参:

1
2
3
4
5
6
7
8
class SemanticAnalyzer(NodeVisitor):
def visit_ProcedureCall(self, node):
for param_node in node.actual_params:
self.visit(param_node)

proc_symbol = self.current_scope.lookup(node.proc_name)
# accessed by the interpreter when executing procedure call
node.proc_symbol = proc_symbol

这样 interpreter 就可以将形参和实参对应起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Interpreter(NodeVisitor):
def visit_ProcedureCall(self, node):
proc_name = node.proc_name

ar = ActivationRecord(
name=proc_name,
type=ARType.PROCEDURE,
nesting_level=2,
)

proc_symbol = node.proc_symbol

formal_params = proc_symbol.formal_params
actual_params = node.actual_params

for formal_param_symbol, actual_param_node in zip(formal_params, actual_params):
ar[formal_param_symbol.name] = self.visit(actual_param_node)

将活动记录推到调用栈上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Interpreter(NodeVisitor):
def visit_ProcedureCall(self, node):
proc_name = node.proc_name

ar = ActivationRecord(
name=proc_name,
type=ARType.PROCEDURE,
nesting_level=2,
)

proc_symbol = node.proc_symbol

formal_params = proc_symbol.formal_params
actual_params = node.actual_params

for param_symbol, argument_node in zip(formal_params, actual_params):
ar[param_symbol.name] = self.visit(argument_node)

self.call_stack.push(ar)

执行程序的主体

在解释阶段遍历 AST 树并访问 ProcedureCall AST 节点时,解释器需要访问对应 ProcedureDecl 节点的 block_node 变量。block_node 变量保存代表过程主体的 AST 子树。可以令语义分析器的 visit_ProcedureDecl 方法访问过程符号和过程主体,即指向过程主体的 AST 子树的 ProcedureDecl AST 节点的 block_node 字段。如下图所示:

相应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ProcedureSymbol(Symbol):
def __init__(self, name, formal_params=None):
...
# a reference to procedure's body (AST sub-tree)
self.block_ast = None


class SemanticAnalyzer(NodeVisitor):
def visit_ProcedureDecl(self, node):
proc_name = node.proc_name
proc_symbol = ProcedureSymbol(proc_name)
...
self.log(f'LEAVE scope: {proc_name}')

# accessed by the interpreter when executing procedure call
proc_symbol.block_ast = node.block_node

这样的话,在过程调用中执行过程主体变得像访问过程声明的 Block AST 节点一样简单,该节点可通过过程的 proc_symbol 的 block_ast 字段访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Interpreter(NodeVisitor):
def visit_ProcedureCall(self, node):
proc_name = node.proc_name

ar = ActivationRecord(
name=proc_name,
type=ARType.PROCEDURE,
nesting_level=2,
)

proc_symbol = node.proc_symbol

formal_params = proc_symbol.formal_params
actual_params = node.actual_params

for param_symbol, argument_node in zip(formal_params, actual_params):
ar[param_symbol.name] = self.visit(argument_node)

self.call_stack.push(ar)

# evaluate procedure body
self.visit(proc_symbol.block_ast)

弹出调用堆栈的活动记录

在我们完成对过程主体的执行以后,过程活动记录将不再需要,所以在离开 visit_ProcedureCall 方法之前将它从调用栈中弹出:

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
26
27
28
29
30
class Interpreter(NodeVisitor):
def visit_ProcedureCall(self, node):
proc_name = node.proc_name

ar = ActivationRecord(
name=proc_name,
type=ARType.PROCEDURE,
nesting_level=2,
)

proc_symbol = node.proc_symbol

formal_params = proc_symbol.formal_params
actual_params = node.actual_params

for param_symbol, argument_node in zip(formal_params, actual_params):
ar[param_symbol.name] = self.visit(argument_node)

self.call_stack.push(ar)

self.log(f'ENTER: PROCEDURE {proc_name}')
self.log(str(self.call_stack))

# evaluate procedure body
self.visit(proc_symbol.block_ast)

self.log(f'LEAVE: PROCEDURE {proc_name}')
self.log(str(self.call_stack))

self.call_stack.pop()

10 执行过程调用 | 《Let’s Build A Simple Interpreter》笔记

http://www.zh0ngtian.tech/posts/68aafb00.html

作者

zhongtian

发布于

2021-08-28

更新于

2023-12-16

许可协议

评论