10 执行过程调用 | 《Let’s Build A Simple Interpreter》
本文最后更新于:2021年9月6日
本文将解读执行过程调用的代码。
创建活动记录
class ARType(Enum):
PROGRAM = 'PROGRAM'
PROCEDURE = 'PROCEDURE'
def visit_ProcedureCall(self, node):
proc_name = node.proc_name
ar = ActivationRecord(
name=proc_name,
type=ARType.PROCEDURE,
nesting_level=2,
)
在活动记录中保存过程实参
以下是描述解释器在活动记录中保存过程参数所需采取的步骤:
- 获取过程的形参列表
- 获取过程的实参列表
- 对于每个形参,获取对应的实参,以形参名称为键,实参求值后作为值保存在过程的活动记录中
过程调用的形参被保存在 ProcedureSymbol 中:
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 数据:
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
这样在语义分析的时候就可以获取到过程调用节点的形参:
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 就可以将形参和实参对应起来:
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)
将活动记录推到调用栈上
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 字段。如下图所示:
相应的代码如下:
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 字段访问:
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 方法之前将它从调用栈中弹出:
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()
评论系统采用 utterances ,加载有延迟,请稍等片刻。