“不怕慢,就怕停” - 中国谚语.
什么是过程声明procedure declaration? 过程声明是一个语言模块,定义了一个标识符(过程的名字)和对应的Pascal代码块。有点像C语言的函数。
严格定义:
- Pascal 过程没有返回值.
- Pascal 过程可以嵌套
- 可以有形参
这个是我们今天的测试Pascal程序:
PROGRAM Part12;
VAR
a : INTEGER;
PROCEDURE P1;
VAR
a : REAL;
k : INTEGER;
PROCEDURE P2;
VAR
a, z : INTEGER;
BEGIN {P2}
z := 777;
END; {P2}
BEGIN {P1}
END; {P1}
BEGIN {Part12}
a := 10;
END. {Part12}
很清楚吧?我们定义了P1和P2两个过程,P2嵌套在P1中。今天我们的任务就是怎么样解析这段代码。
首先我们需要把过程声明这个新物种加入我们的语法中。
过程声明的定义里面包含了一个block规则,一图胜万言。
更新后的句法图是这样的:
对于下面这个代码,P1和P1A是同一个层次的声明:
PROGRAM Test;
VAR
a : INTEGER;
PROCEDURE P1;
BEGIN {P1}
END; {P1}
PROCEDURE P1A;
BEGIN {P1A}
END; {P1A}
BEGIN {Test}
a := 10;
END. {Test}
过程声明当然也可以是嵌套的。因为过程声明的规则里面,有个block规则,而block规则是包含声明规则的,声明规则有包含着过程声明规则。
好的,下面我们看解释器的更新部分。
更新 Lexer
仅仅是增加一个新的 PROCEDURE token PROCEDURE = 'PROCEDURE'。
另外把 ‘PROCEDURE’ 作为一个新的保留关键字。
加上PROCEDURE,所有的关键字如下:
RESERVED_KEYWORDS = {
'PROGRAM': Token('PROGRAM', 'PROGRAM'),
'VAR': Token('VAR', 'VAR'),
'DIV': Token('INTEGER_DIV', 'DIV'),
'INTEGER': Token('INTEGER', 'INTEGER'),
'REAL': Token('REAL', 'REAL'),
'BEGIN': Token('BEGIN', 'BEGIN'),
'END': Token('END', 'END'),
'PROCEDURE': Token('PROCEDURE', 'PROCEDURE'),
}
升级Parser
- 增加新的ProcedureDecl AST 节点
- 更新Parser的 declarations 方法,支持 procedure declarations
一下是细节:
- ProcedureDecl AST 节点表示 procedure declaration. 它实现方式是Python的类,构造函数接受过程的名字和代码块node(本身是AST node).
class ProcedureDecl(AST): def __init__(self, proc_name, block_node): self.proc_name = proc_name self.block_node = block_node
- declarations 方法更新。这个方法还记得吗?在 Parser 类里面。
def declarations(self): """declarations : VAR (variable_declaration SEMI)+ | (PROCEDURE ID SEMI block SEMI)* | empty """ declarations = [] if self.current_token.type == VAR: self.eat(VAR) while self.current_token.type == ID: var_decl = self.variable_declaration() declarations.extend(var_decl) self.eat(SEMI) while self.current_token.type == PROCEDURE: self.eat(PROCEDURE) proc_name = self.current_token.value self.eat(ID) self.eat(SEMI) block_node = self.block() proc_decl = ProcedureDecl(proc_name, block_node) declarations.append(proc_decl) self.eat(SEMI) return declarations
更新 SymbolTable builder
这部分先留空给下一章。
def visit_ProcedureDecl(self, node):
pass
更新 Interpreter
对 visit_ProcedureDecl 方法,我们也留空。
我们看看加入了ProcedureDecl 新节点后,AST长什么样子:
PROGRAM Part12;
VAR
a : INTEGER;
PROCEDURE P1;
VAR
a : REAL;
k : INTEGER;
PROCEDURE P2;
VAR
a, z : INTEGER;
BEGIN {P2}
z := 777;
END; {P2}
BEGIN {P1}
END; {P1}
BEGIN {Part12}
a := 10;
END. {Part12}
用genastdot.py 工具来生成AST:
$ python genastdot.py part12.pas > ast.dot && dot -Tpng -o ast.png ast.dot
我们试试老的解释器能不能工作。还行,没有崩掉。
$ python spi.py part12.pas
Define: INTEGER
Define: REAL
Lookup: INTEGER
Define: <a:INTEGER>
Lookup: a
Symbol Table contents:
Symbols: [INTEGER, REAL, <a:INTEGER>]
Run-time GLOBAL_MEMORY contents:
a = 10