不要擔(dān)心走得緩慢捏悬;害怕原地不動(dòng)! - 中國諺語(我表示懷疑H筇荨9馈!)
大家好纺铭,歡迎回來寇钉!
今天我們將要走幾小步來學(xué)習(xí)如何識(shí)別Pascal過程聲明。
什么是過程聲明彤蔽?一個(gè)過程聲明是一個(gè)語言結(jié)構(gòu)摧莽,它定義了一個(gè)標(biāo)記符,以及一個(gè)關(guān)聯(lián)的Pascal代碼塊顿痪。
在我們深入討論之前镊辕,說幾句關(guān)于Pascal過程以及他們的聲明:
- Pascal過程沒有返回語句,在到達(dá)程序塊末尾的時(shí)候退出蚁袭。
- Pascal過程可以相互嵌套征懈。
- 簡化起見,這篇文章介紹的過程聲明不帶有參數(shù)揩悄。但是卖哎,不要擔(dān)心,我們會(huì)在后續(xù)系列覆蓋這部分內(nèi)容删性。
這是我們今天的測試程序:
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}
就像你看到的亏娜,我們定義了兩個(gè)過程(P1和P2),P2嵌套在P1中蹬挺。在上述代碼中维贺,我用帶有過程名字的注釋來標(biāo)記各個(gè)過程的開始位置和結(jié)束位置。
我們今天的目標(biāo)相當(dāng)明確:學(xué)習(xí)如何識(shí)別上面那樣的代碼巴帮。
首先溯泣,為了添加過程聲明虐秋,我們需要調(diào)整文法。好了垃沦,我們開始客给!
過程聲明子規(guī)則包含保留字--PROCEDURE,后面跟著一個(gè)標(biāo)記符(過程名字)肢簿,再跟一個(gè)分號靶剑,分號后面跟著代碼塊,最后以另外一個(gè)分號結(jié)尾池充。
這里是更新后的語法圖抬虽,表示聲明規(guī)則:
從文法以及上述語法圖可以看出,你可以聲明任意數(shù)量想要的過程纵菌。例如,在下面這個(gè)代碼片段中休涤,我們定義了兩個(gè)過程聲明咱圆,P1和P2,位于同一層級功氨。
PROGRAM Test;
VAR
a : INTEGER;
PROCEDURE P1;
BEGIN {P1}
END; {P1}
PROCEDURE P1A;
BEGIN {P1A}
END; {P1A}
BEGIN {Test}
a := 10;
END. {Test}
上面的文法和圖表也表明過程聲明是可以嵌套的序苏,因?yàn)檫^程聲明子規(guī)則引用了程序塊規(guī)則,而程序塊規(guī)則又包含聲明規(guī)則捷凄,聲明規(guī)則包含過程聲明忱详,繼而過程聲明是嵌套的。作為提醒跺涤,這里是第十部分關(guān)于程序塊規(guī)則的一個(gè)語法圖和文法:
好了匈睁,現(xiàn)在讓我們把目光聚焦到解釋器組件,它也需要更新來支持過程聲明:
更新詞法分析器
我們只要添加新的標(biāo)識(shí)符--PROCEDURE就可以了:
PROCEDURE = 'PROCEDURE'
把PROCEDURE添加進(jìn)保留字中桶错。這里是一份保留字到標(biāo)識(shí)符的完整映射:
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'),
}
更新語法分析器
語法分析器變化總結(jié):
- 新的ProcedureDecl 抽象語法樹節(jié)點(diǎn)
- 更新語法分析器中過程聲明對應(yīng)的方法航唆,支持過程聲明
實(shí)現(xiàn)這些變化.
1. ProcedureDecl抽象語法樹節(jié)點(diǎn)代表了一個(gè)過程聲明。類構(gòu)造函數(shù)以過程名字以及該過程名字指向的程序塊節(jié)點(diǎn)為參數(shù)院刁。
class ProcedureDecl(AST):
def __init__(self, proc_name, block_node):
self.proc_name = proc_name
self.block_node = block_node
2. 這是更新后的語法分析器的聲明方法
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
正如期待的糯钙,上面代碼是相當(dāng)說明性質(zhì)的。它按照前面介紹的文法/語法圖來設(shè)計(jì)退腥。
更新符號表生成器
由于我們目前還不能處理嵌套的過程作用域任岸,所以只是簡單地給SymbolTreeBuilder抽象語法樹訪問者類添加一個(gè)空的visit_ProcedureDecl方法。下一篇文章解決這個(gè)問題狡刘。
def visit_ProcedureDecl(self, node):
pass
更新解釋器
添加一個(gè)空的visit_ProcedureDecl方法享潜,用來讓我們的解釋器忽略所有過程聲明。
So far, so good.
既然我們已經(jīng)做好了這些必要的準(zhǔn)備颓帝,讓我們看看帶有ProcedureDecl節(jié)點(diǎn)的抽象語法樹長什么樣子米碰。
這是我們的Pascal程序(你可以從Github直接下載):
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工具可視化。
$ python genastdot.py part12.pas > ast.dot && dot -Tpng -o ast.png ast.dot
在上述的圖形中吕座,你能看到兩個(gè)ProcedureDecl節(jié)點(diǎn):ProcDecl:P1和ProcDecl:P2虐译,分別對應(yīng)過程P1和過程P2。完成使命吴趴!
作為今天最后的一個(gè)條目漆诽,我們快速確認(rèn)一下我們更新后的解釋器能如從前般工作,處理好帶有過程聲明的Pascal程序锣枝。如果還沒有下載厢拭,請下載解釋器和測試程序,在命令行運(yùn)行撇叁。你的輸出看上去應(yīng)該類似這樣:
$ python spi.pi 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
好供鸠, 所有知識(shí)和經(jīng)驗(yàn)都被我們拿下了,我們準(zhǔn)備解決嵌套作用域的問題陨闹。理解嵌套作用域能幫助我們分析嵌套過程以及讓我們能夠處理過程和函數(shù)調(diào)用楞捂。這就是我們在下一篇文章中將要做的事情:深入理解嵌套作用域。下次見趋厉。