? ? ? ? 這一部分,我們研究語義分析中剩下的的流程和類型檢查。
6.2 流程檢查
? ? ? ? 還是以我們前面舉例使用的那段源代碼作為例子,經(jīng)過聲明檢查的錯(cuò)誤提醒,可以改成:
int main()
{
int a = 1;
return;
}
很重要的一步。但是,這段代碼仍然是有語法錯(cuò)誤的,因?yàn)闆]有返回值。這個(gè)錯(cuò)誤將會(huì)很快檢查出來。
? ? ? ? 我們還是遍歷那棵抽象語法樹,與聲明檢查重點(diǎn)遍歷變量不同,流程檢查將重點(diǎn)遍歷語句。這一點(diǎn)是非常直觀,也是容易理解的。仿照聲明檢查的方法,我們?cè)俣x一個(gè)遍歷的類,然后定義將會(huì)用到的節(jié)點(diǎn)函數(shù)。
class FlowControlVisitor(NodeVisitor):
def visit_AST(self, node):
node.has_return = False
def visit_TranslationUnit(self, node):
self.visit_list(node.nodes)
def visit_FunctionDefn(self, node):
node.body.visit(self)
if node.return_type != VOID and not node.body.has_return:
raise Exception("Function doesn't return through all branches." )
def visit_CompoundStatement(self, node):
node.statements.visit(self)
node.has_return = node.statements.has_return
def visit_StatementsList(self, node):
node.has_return = False
for stmt in node.nodes:
stmt.visit(self)
def visit_ReturnStatement(self, node):
node.has_return = True
為了實(shí)現(xiàn)返回值檢查,我們?yōu)檫@些節(jié)點(diǎn)定義了has_return的屬性。這個(gè)屬性從節(jié)點(diǎn)FunctionDefn逐漸滲透到模板節(jié)點(diǎn),只在遇到節(jié)點(diǎn)ReturnStatement時(shí)才返回真,這樣,當(dāng)再次回溯到FunctionDefn節(jié)點(diǎn)時(shí),就可以做出判斷,返回值檢查也就實(shí)現(xiàn)了。
? ? ? ? 此外,在流程檢查中,還有一類錯(cuò)誤需要關(guān)注:關(guān)鍵字break和continue只能在循環(huán)語句中才能使用。這部分內(nèi)容,我們重點(diǎn)關(guān)注循環(huán)節(jié)點(diǎn)及其相關(guān)的continue, break節(jié)點(diǎn):
def __init__(self):
self.in_loop = False
def visit_ForLoop(self, node):
self.in_loop = True
node.stmt.visit(self)
def visit_BreakStatement(self, node):
if not self.in_loop:
raise Exception('Break statement outside of loop.')
def visit_ContinueStatement(self, node):
if not self.in_loop:
raise Exception('Continue statement outside of loop.')
通過引入成員變量in_loop,進(jìn)入函數(shù)體時(shí)初始化為假,而只在循環(huán)節(jié)點(diǎn)入口才設(shè)置in_loop為真,之后再遍歷到return和break節(jié)點(diǎn),語法就是正確的,否則語法錯(cuò)誤。但是,對(duì)于下面這種情況:
int main() // in_loop = false 0
{
int k = 0;
for (int j = 0; j < 10; j = j + 1) { // in_loop = true 1
for (int i = 0; i < 10; i = i + 1) { // in_loop = true 2
if (i > 5)
break;
}
int i = 0;
if (i < 5) { // in_loop = true 1
i = i + 1;
continue;
}
}
if (k < 5) { // in_loop = false 0
continue;
}
}
continue出現(xiàn)在結(jié)構(gòu)體外,但是由于第一個(gè)循環(huán)體改變了in_loop的屬性而沒有復(fù)原,使得對(duì)continue的檢查將出現(xiàn)錯(cuò)誤。此外,也不能簡單地將其重置為假,因?yàn)檠h(huán)是可以嵌套的,否則將改變in_loop原來的正確狀態(tài)。因此,采用棧結(jié)構(gòu)對(duì)變量進(jìn)行保存:
def visit_ForLoop(self, node):
super_in_loop = self.in_loop
self.in_loop = True
node.stmt.visit(self)
self.in_loop = super_in_loop
用super_in_loop來暫時(shí)保存當(dāng)前的in_loop屬性,當(dāng)遍歷離開循環(huán)節(jié)點(diǎn)時(shí),又恢復(fù)到原來的狀態(tài)。
? ? ? ? 最后,合并has_return和in_loop的使用,就實(shí)現(xiàn)了流程檢查。
6.3 類型檢查
? ? ? ? 我們先來看下面這段C代碼:
int *a;
int b;
a = *b; // error 1
b = a; // error 2
a = &5; // error 3
這段代碼,我們列出了三個(gè)語法錯(cuò)誤,分別是:
- error 1: b不是指針類型
- error 2: 將指針類型轉(zhuǎn)換到int類型
- error 3: 對(duì)右值取地址
這些就是類型檢查將要做的事情。我們?cè)谇懊婊ㄙM(fèi)很大力氣定義的那些類型節(jié)點(diǎn),它們將在這里進(jìn)行排上用場。同樣地,再定義一個(gè)遍歷類:
class TypeCheckVisitor(NodeVisitor):
pass
在定義節(jié)點(diǎn)函數(shù)之前,我們先定義一個(gè)葉子函數(shù),也就是輔助判斷函數(shù),來幫助我們比較兩個(gè)變量的類型:
def compare_types(self, from_type, to_type):
conflict = False
from_str = from_type.to_str()
to_str = to_type.to_str()
if from_str != to_str:
if from_str == 'char':
if to_str == 'int':
pass
else:
conflict = True
else:
conflict = True
if conflict:
raise Exception(f"Cannot convert from {from_str} to {to_str}.")
通過函數(shù)to_str()(在類型節(jié)點(diǎn)中添加為成員函數(shù))獲取將要比較的兩個(gè)type的具體類型,我們?cè)试Schar到int的轉(zhuǎn)換,但對(duì)于其它情況,將會(huì)拋出異常。
? ? ? ? 接下來,我們定義所關(guān)心的節(jié)點(diǎn)函數(shù):
def visit_AST(self, node):
pass
def visit_AddrOf(self, node):
node.expr.visit(self)
if not node.expr.is_lvalue():
raise Exception(f"Address-of (&) target has no address!")
def visit_Pointer(self, node):
node.expr.visit(self)
if node.expr.type.to_str() == 'pointer':
node.set_lvalue()
else:
raise Exception(f"Pointer dereference (*) target is not a pointer!")
def visit_BinOp(self, node):
node.left.visit(self)
node.right.visit(self)
if node.op == '=':
if not node.left.is_lvalue():
raise Exception(f"{node.left.expr} is an invalid lvalue: not an address!.")
self.compare_types(node.right.type, node.left.type)
在前面,我們通過派生出具體的類型來羅列單目運(yùn)算符的表達(dá)式,這里就具有實(shí)際意義了。因?yàn)?,我們可可以單?dú)創(chuàng)建節(jié)點(diǎn)函數(shù)進(jìn)行處理。在BinOp中,通過調(diào)用compare_types,我們解決了error 2;通過定義節(jié)點(diǎn)Pointer的函數(shù),我們解決了error 1;而對(duì)于error 3,可用上面代碼中的兩個(gè)函數(shù)set_lvalue()和is_lvalue()來輔助判斷。
? ? ? ? 那么,那些值是左值呢?我們需要回到最開始聲明檢查中定義的那個(gè)節(jié)點(diǎn)函數(shù)里去判斷:聲明過的變量,我們將其加入_symbols中,如果后面使用的變量就在這個(gè)表中,那么這個(gè)變量就認(rèn)為是左值,因?yàn)樗环峙淞舜鎯?chǔ)空間。具體細(xì)節(jié),我們將在生成匯編代碼中進(jìn)一步說明。這樣,修改那里的代碼為:
def visit_Id(self, node):
symbol = self.current_scope.lookup(node.expr)
if symbol is not None:
node.set_lvalue()
else:
comment = f"Identifier '{node.expr}' not found."
raise Exception(comment)
這樣,就和TypeCheckVisitor關(guān)聯(lián)上了。當(dāng)然,還有其它地方需要進(jìn)行類型比較的地方,比如ReturnStatement節(jié)點(diǎn):
def visit_FunctionDefn(self, node):
self.curr_func = node
node.body.visit(self)
def visit_ReturnStatement(self, node):
node.expr.visit(self)
return_type = self.curr_func.return_type
self.compare_types(node.expr.type, return_type)
我們同樣增加了一個(gè)成員變量curr_func,使得我們能夠在ReturnStatement節(jié)點(diǎn)中拿返回值的類型與函數(shù)定義的返回值類型進(jìn)行比較,從而發(fā)現(xiàn)錯(cuò)誤。
? ? ? ?
? ? ? ? 語義分析的內(nèi)容基本上就到此為止。這里,我們只是簡單地覆蓋了C語言中非常常見的語法錯(cuò)誤。但是,通過熟悉了遍歷方式,定義節(jié)點(diǎn)函數(shù)訪問節(jié)點(diǎn)進(jìn)行節(jié)點(diǎn)內(nèi)容分析,大家可以觸類旁通,通過不斷添加和完善節(jié)點(diǎn)函數(shù),繼續(xù)在這棵抽象語法樹上進(jìn)行更多的語法檢查。
實(shí)現(xiàn)簡易的C語言編譯器(part 0)
實(shí)現(xiàn)簡易的C語言編譯器(part 1)
實(shí)現(xiàn)簡易的C語言編譯器(part 2)
實(shí)現(xiàn)簡易的C語言編譯器(part 3)
實(shí)現(xiàn)簡易的C語言編譯器(part 4)
實(shí)現(xiàn)簡易的C語言編譯器(part 5)
實(shí)現(xiàn)簡易的C語言編譯器(part 6)
實(shí)現(xiàn)簡易的C語言編譯器(part 7)
實(shí)現(xiàn)簡易的C語言編譯器(part 8)
實(shí)現(xiàn)簡易的C語言編譯器(part 10)
實(shí)現(xiàn)簡易的C語言編譯器(part 11)