在初學編譯原理時的第二章中就給了制導翻譯程序的例子,在第五章中又再次提到,不過更加詳細。
上次做的預測分析器還有不完善的地方——目前只能進行語法分析,不能產生語義動作。可以使用語法制導定義的方法來完成。
語法制導定義是上下文無關文法和屬性以及規則的結合,屬性與文法符號相關聯,規則和產生式相關聯。非終結符具有兩種屬性:綜合屬性和繼承屬性。分析樹上的非終結符的綜合屬性是由產生式所關聯的語義規則來定義的,其來源爲自身屬性以及其子結點的屬性。繼承屬性是由父結點上所關聯的語義規則定義,其屬性值的來源是父結點、本身以及兄弟結點。
各個結點的屬性值是存在依賴關係的,如3+2這樣一個簡單的表達式,以語法樹的形式表示出來時,最終求出值的父結點的值依賴於兩個子結點的值(先得到3、2,然後才計算得到5)。因此求值必須要按照一定的順序。書中提到的具體求值順序的算法涉及到拓撲排序,但因爲我所要編的編譯器的語法是比較簡單的,只需要求綜合屬性即可,因此無需該算法。
如何將語義的內容附到之前的預測分析結構中去?我的思路是這樣的:
建立一個語義單元結構,用以對應於某一個產生式,它關聯一個語義動作,這個動作將在配置文件中定義。這個語義單元的對象在每一個非終結符的產生式展開時創建,創建後壓入一個棧中,表示當前結點的父結點,當終結符匹配時,進行求值,並將結果通過棧返回給父結點,當父結點子結點均求值完畢時,彈出並將結果繼續返回,直到棧空,求出最終值。這其實就是一個用棧模擬先序遍歷的過程。代碼如下:
void nc_parser::translate(string code,string mode){//mode爲起點的選擇 通常爲產生式的起始符 但由於NC程序的特殊,這裏當默認爲語句stmt(指令的翻譯佔大多數)
syn_unit end(0);
syn_unit start(mode, mesh_table[mode]->src.lex, 0, syn_unit::NONTERMINATOR);
stack<syn_unit> stk;
stack<smt_unit> smt_stk;//維護一個語義棧 當產生式展開時將其壓入棧中(作爲父節點存在) 用於獲取匹配的值
stk.push(end);
stk.push(start);
int pos = 0;
auto lx_unit = lex->getNextToken(code,pos);
while (stk.top() != end){// && pos < code.length()
auto&cur_sig = stk.top();//當前
//查表決定展開式:
auto type = cur_sig.type;
switch (cur_sig.type){//符號類型
case syn_unit::EMPTY://匹配空符前 先清算父結點
case syn_unit::LEX://終結符
if (cur_sig.type == syn_unit::EMPTY || lx_unit->compare(cur_sig.token)){//類型是否匹配
cur_sig.buf = lx_unit->get_result();//匹配語法單元
auto*sem = &smt_stk.top();//當前語義處理塊
sem->calculate(&cur_sig);//計算屬性值
while (!smt_stk.empty() && sem->is_calcu_over){
//如果該語義塊計算完了 就輸出/把值通過棧交給父結點
sem->output();
auto p = *sem;//臨時存儲當前彈出值
smt_stk.pop();
if (!smt_stk.empty()){
sem = &smt_stk.top();
sem->calculate(&p);
}
}//彈出棧頂所有計算完畢的父節點
stk.pop();
}
else{//失配 嘗試別的?
report("不匹配的類型:"+lx_unit->token);
}
if (type!=syn_unit::EMPTY)//句子沒掃描完才繼續讀token
lx_unit = lex->getNextToken(code,pos);//匹配後才後移
break;
case syn_unit::NONTERMINATOR:
auto&prod = mesh_table[cur_sig.token];//獲取產生式組
if (prod->drivetable.find(lx_unit) != prod->drivetable.end() || prod->drivetable.find(NULL) != prod->drivetable.end()){
//該單詞在驅動表中
stk.pop();
int choice;
if (prod->drivetable.find(lx_unit) != prod->drivetable.end())
choice = prod->drivetable[lx_unit];
else
choice = prod->drivetable[NULL];
auto&dst = prod->dst[choice];//選擇產生式
for (auto&var = dst.rbegin(); var != dst.rend(); var++)
stk.push(*var);
//展開並逆序壓入棧
smt_stk.push(smt_unit(prod, choice));
//維護語義棧
}
break;
}
}
}
目前面臨的難題是如何來定義配置文件,使之可以表達出想要的語義動作,並且能夠檢查語義規範。
struct smt_unit{
public:
static map<production*,smt_action*>table;
static void init();
private:
production*pd;//產生式指針
int cho;//選擇哪一個產生式
int cur;//當前計算完成數
string result;//計算結果
public:
vector<string>value;
void calculate(syn_unit*);//求值 傳入語法單元
void calculate(smt_unit*);//求值 傳入其他語義單元的結果
bool is_calcu_over;
void output();//輸出值
smt_unit(production*,int);
};
目前在語義單元結構中定義了一個映射表,將產生式與動作相關聯,但smt_action還沒有定義。目前只能將各個字符串連接並輸出:
可以看出是一個深度遍歷的過程。