? ? ? ? 這一部分角虫,我們研究語義分析中剩下的的流程和類型檢查醇滥。
6.2 流程檢查
? ? ? ? 還是以我們前面舉例使用的那段源代碼作為例子离例,經(jīng)過聲明檢查的錯(cuò)誤提醒,可以改成:
int main()
{
int a = 1;
return;
}
很重要的一步把还。但是,這段代碼仍然是有語法錯(cuò)誤的茸俭,因?yàn)闆]有返回值吊履。這個(gè)錯(cuò)誤將會很快檢查出來。
? ? ? ? 我們還是遍歷那棵抽象語法樹调鬓,與聲明檢查重點(diǎn)遍歷變量不同艇炎,流程檢查將重點(diǎn)遍歷語句。這一點(diǎn)是非常直觀袖迎,也是容易理解的冕臭。仿照聲明檢查的方法,我們再定義一個(gè)遍歷的類燕锥,然后定義將會用到的節(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ò)誤宋列。但是昭抒,對于下面這種情況:
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ù)原炼杖,使得對continue
的檢查將出現(xiàn)錯(cuò)誤灭返。此外,也不能簡單地將其重置為假嘹叫,因?yàn)檠h(huán)是可以嵌套的婆殿,否則將改變in_loop
原來的正確狀態(tài)。因此罩扇,采用棧結(jié)構(gòu)對變量進(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: 對右值取地址
這些就是類型檢查將要做的事情捞高。我們在前面花費(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的具體類型冗尤,我們允許char
到int
的轉(zhuǎn)換,但對于其它情況胀溺,將會拋出異常裂七。
? ? ? ? 接下來,我們定義所關(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澎埠;而對于error 3虽缕,可用上面代碼中的兩個(gè)函數(shù)set_lvalue()
和is_lvalue()
來輔助判斷。
? ? ? ? 那么蒲稳,那些值是左值呢氮趋?我們需要回到最開始聲明檢查中定義的那個(gè)節(jié)點(diǎn)函數(shù)里去判斷:聲明過的變量,我們將其加入_symbols
中江耀,如果后面使用的變量就在這個(gè)表中剩胁,那么這個(gè)變量就認(rèn)為是左值,因?yàn)樗环峙淞舜鎯臻g祥国。具體細(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)