通過詞法分析,我們成功得到了一個完整的token 文件以及符號表,接下來要做的就是語法/語義分析。我們採用的分析方法是算符優先算法,實現這一個算法的前提是文法必須是算符優先文法,因此我們首先要做的事就是構造算符優先文法,文法結構如下:
1、構造文法並且初始化其各個屬性。
class Grammar
{
public:
int getid(){ return id ; }
char * getINP(){ return INP; }
char *getOUP(){ return OUP; }
bool isbelong(string str) ; //是否屬於終結符集合
vector<string> getisEnd(){ return isEnd; }
private:
int id; //文法序號
char * INP; //產生式左部分
char * OUP;//產生式右部分數組
vector<string> notEnd; //非終結符集合
vector<string> isEnd; //終結符集合
};
本次實驗所用的算符文法 grammar 數組集合:
Grammar grammar[] = {
Grammar(0, "W", "# P #"),
Grammar(1, "P", "program id L"), //程序,標識符,程序體
Grammar(2, "L", "S ; L") ,//S語句 , L 語句表, A賦值語句,B 布爾表達式,E算術表達式
Grammar(2, "L", "S"),
Grammar(3, "S", "if B then S"),
Grammar(3, "S", "if B then L else S"),
Grammar(3, "S", "while B do S"),
Grammar(3, "S", "begin L end"),
Grammar(3, "S", "var D"),
Grammar(3, "S", "?"), //s->空 //10個
Grammar(3, "S", "A"),
Grammar(4, "D", "id : K ;"), //D 聲明語句 , id標識符 ,K數據類型
Grammar(5, "K", "integer"),
Grammar(5, "K", "bool"),
Grammar(5, "K", "real"), //156
Grammar(6, "A", "id := E"),
Grammar(7, "E", "E + T"),
Grammar(7, "E", "T"),
Grammar(7, "E", "- E"),
Grammar(8, "B", "B or N"), //20
Grammar(8, "B", "N"),
Grammar(8, "B", "not B"), //R 布爾運算符
Grammar(9, "T", "T * F"),
Grammar(9, "T", "F"),
Grammar(10, "F", "( E )"), //25
Grammar(10, "F", "id"),
Grammar(11, "N", "N and M"),
Grammar(11, "N", "M"),
Grammar(12, "M", "( B )"),
Grammar(12, "M", "id < id"),//30
Grammar(12, "M", "id > id"),
Grammar(12, "M", "id <> id"),
Grammar(12, "M", "id <= id"),
Grammar(12, "M", "id >= id"),
Grammar(12, "M", "id = id"),
};
例如Grammar(1, "P", "program id L"),表示P->program id L 產生式,程序中標識符、整形、浮點型和常數在產生式統一體現爲”id”,文法序號用來區分產生式左部的非終結符,此文法中,非終結字符全用大寫字母表示,終結字符全用小寫字母表示,這樣區分是否爲終結符只需要判斷字符串轉化成大寫後是否等於本身即可。
2、將所有文法根據產生式左部統一起來
完成了文法的構建,接下來要把產生式統一起來,結構如下:
class flGrammar
{
private:
vector<string> isEnd;
string notEnd; //左產生式
int id;
public:
vector<string> fristvt;
vector<string> lastvt;
flGrammar(){};
int getid(){ return id; }
string getNotEnd(){ return notEnd; }
flGrammar(int id){ flInitGrammar(id); };
void flInitGrammar(int id);
bool isexistFirstvt(string str) //是否在fristvt和lastvt
{
for (int i = 0; i < fristvt.size(); i++)
if (str == fristvt[i])
return true;
return false;
}
bool isexistLastvt(string str);
};
本次要做的主要任務爲求得Fristvt和Lastvt的第一步,也就是把產生式第一個終結符加入到Fistvt,如果第一個是非終結符把第二個字符串姐加入到Fristvt,Lastvt同理。
通過初始化工作,得到一個整合起來的文法,例如 S->if B then S,S->begin L end,整合起來後得到notend=”S” ,isend={if then begin end},fristvt={if begin},lastvt={then end},其中flGrammar的屬性id也就是文法序號。
3、構造文法表,完善Fristvt和lastvt
class flTable
{
public:
flTable();
~flTable();
void print(){
for (int i = 0; i < Grammar_Maxid; i++)
flgrammar[i]->print();
}
void finishGra(); //完成Fristvt & Lastvt
void printTable();
bool Match(vector<token>,string &);
void printBuffer();
void printCreateTreeStack();
void printSignStack();
Tree * getGraTree(){ return GrammarTree; }
private:
flGrammar *flgrammar[Grammar_Maxid];
vector<vecEnd> end; //終結符聚合
int **t_relation;
void initEnd();
bool isexistEnd(vecEnd str)
{
for (int i = 0; i < end.size(); i++)
if (str.end == end[i].end)
return true;
return false;
}
int findend(string str) ; //根據字符串查詢終結符在end的中的索引
vector <token> signStack; //符號棧
vector <token> Buffer;//輸入緩衝區
string getSignStackname(token t)
{
if (typeIsId(t.type))
return "id";
return t.name;
}
Tree *GrammarTree = NULL;
};
其中flGrammar *flgrammar[Grammar_Maxid];也就是關於所有文法的集合,之前已經把產生式第一個終結符加入到Fristvt,把產生式最後一個終結符加入到Lastvt中。現在要完善Fristvt和Lastvt。從尾至頭的遍歷garmmar文法數組集合,把形如P->Q……這樣非終結符Q打頭的Fristvt集加入到P的Fristvt集裏,形如P->……Q這樣非終結符結尾的Q的Lastvt集加入到P的lastvt,在加入的過程中首先要判斷要加入的終結符是否已經在了集合中。這就是bool flGrammar:: isexistFirstvt(string str) 的作用,如果已經在集合中就不用再加入進去了。
4、初始化終結符集合,完成算符優先表
void flTable::initEnd();終結符集合比較簡單,只需要遍歷文法的終結符集合即可。初始化算符優先表,把P->…Qa… 這樣的Lastvt(Q)中所有元素> a ,把P->…aQ…這樣的a<Fristvt(Q)中所有元素,把P->…ab…或P->…aQb這樣的另a=b。其中用0、1、2、3分別代表無關係、小於關係、等於關係、大於關係。
細化算符優先表int **t_relation;步驟
1、遍歷grammar文法集合,從第0個文法開始,將其產生式右部分進行字符串分割,得到一個該產生式右部字符集合,遍歷該字符集合,從第i=0個字符開始判斷是不是終結符,如果是,再判斷第i+1個字符是不是非終結符,如果是,第i個終結符小於第i+1個非終結符的Fristvt集合,如果第i+1是終結符,則第i個等於第i+1個。
2、如果第i個是非終結符則第i個的非終結符的Lastvt集合大於第i+1個字符。
3、再次遍歷一遍文法,如果第i=0個字符爲終結符且第i+1個爲非終結符且第i+2個爲終結符,則第i個終結符等於第i+2個終結符。
注意:終結符在算符優先表的位置也就是在vector<vecEnd> end; //終結符集合中的位置,所以有 int flTable:: findend(string str)的作用就是查找終結符在終結符集合中的索引。
5、初始化符號棧,輸入緩衝區,根據算符優先表完成規約
先自定義一個有 “#”的token對象,將#壓入棧中和緩衝區中,再將詞法分析中產生的token集合逆序推入緩衝區。我們知道,在規約整個過程中都是棧與緩衝區的終結符之間的關係比較,與非終結符無關。所以規約時,只需要把棧中元素推出去即可,無需再把非終結符壓入棧中。
當棧頂元素與緩衝區頂元素比較,如果小於或等於,把緩衝區頂元素移進到棧中。如果大於則棧頂指針top下移,top和top+1元素進行比較直到比較到小於時,將top之後的元素全部從棧中推出。棧頂元素與緩衝區頂元素無關係時,則指明語法出錯。當且僅當關係爲等於號且緩衝區頂部元素爲#時表明成功規約。至此語法分析結束。
6、語義分析:完成語義動作表以及四元式數據結構
四元式結構如下:其中op代表第一元素的字符形式,code代表第一元素代碼形式。ConvertOpToCode()方法是根據op給code賦值。完成後,定義一個四元式集合,語義分析就是完成代碼關於四元式集合的生成。
struct GenStruct
{
int label; //四元式序號
string op;
int code; //第一元素
string addr1="0"; //第二元素
string addr2="0"; //第三元素
string result="0";//第四元素
int out_port = 0; //記錄該四元式是否爲一個基本塊的入口,是則爲1,否則爲0。
void ConvertOpToCode(string);
GenStruct(int l){ label = l; }
GenStruct(int l,string op,string a1,string a2,string res){
label = l, this->op=op, ConvertOpToCode(op), addr1 = a1, addr2 = a2, result = res;
}
};
語義動作表代碼在語法分析中規約的時候完成:
void GrammarAction(TreeNode *pNew, vector<TreeNode> pushend,int index) //語義動作 index代表其文法索引, pushend代表各個結點 pNew 根結點
{
string value1;
string backpatchfalse;
switch (index)
{
#pragma region ...
case 0:
pNew->nextlist = pushend[1].nextlist;
break;
case 1:
pNew->nextlist = pushend[0].nextlist;
break;
case 2:
pNew->nextlist = pushend[0].nextlist;
pNew->fristnextquad = pushend[2].fristnextquad;
break;
case 3:
pNew->nextlist = pushend[0].nextlist;
pNew->fristnextquad = pushend[2].fristnextquad;
break;
//S
case 4: //if B then S
backpatch(pushend[pushend.size() - 2].truelist, pushend[pushend.size() - 2].nextquad);
pNew->nextlist = merge(pushend[0].nextlist, pushend[pushend.size() - 2].falselist);
pNew->fristnextquad = pushend[pushend.size() - 2]].fristnextquad;
break;
case 5://if B then L else S,then 要跳轉到 else後面
int2str(stoi(pushend[2].nextquad)+1,backpatchfalse);
backpatch(pushend[pushend.size() - 2].truelist, pushend[pushend.size() - 2].nextquad);
backpatch(pushend[pushend.size() - 2].falselist, backpatchfalse);
//backpatch(pushend[pushend.size() - 2].falselist, pushend[0].fristnextquad);
pNew->nextlist = merge(pushend[0].nextlist, pushend[pushend.size() - 2].falselist);
CreateGen("j", "0", "0", pushend[0].nextquad);
pNew->nextquad = getNextquad();
//把getnextquad的產生式插入到pushend[2].nextquad ,把13加入到7後面8
InsertGentoIndex(getNextquad(), pushend[2].nextquad);
pNew->fristnextquad = pushend[pushend.size() - 2]].fristnextquad;
break;
case 6://s->while B do S
/* backpatch(pushend[0].nextlist,pushend[2].fristnextquad);*/
backpatch(pushend[2].truelist, pushend[2].nextquad);
pNew->nextlist = pushend[2].falselist;
CreateGen("j", "0", "0", pushend[2].fristnextquad);
pNew->nextquad = getNextquad();
backpatch(pushend[2].falselist, getNextquad());
break;
case 7://begin S end
pNew->nextlist = pushend[1].nextlist;
break;
case 8://var D
break;
case 9:// ?
break;
case 10://S->A
break;
case 11: //D:->id:K
break;
case 12://k->integer
break;
case 13://k->integer
break;
case 14://k->integer
break;
case 15://A-> id:=E
CreateGen(pushend[1].data.name, pushend[0].value, "0", pushend[2].data.name);
pNew->nextquad = getNextquad();
break;
case 16://E->E+T
value1 = NewTempStruct();
CreateGen("+", pushend[2].value, pushend[0].value, value1);
pNew->value = value1;
pNew->nextquad = getNextquad();
break;
case 17:
pNew->value = pushend[0].value;
break;
#pragma endregion
case 18: //E->-E
value1 = NewTempStruct();
CreateGen("-","0", pushend[0].value, value1);
pNew->nextquad = getNextquad();
pNew->value = value1;
break;
case 19://B-> B or N
backpatch(pushend[pushend.size() - 1].falselist, pushend[pushend.size() - 1].nextquad);
pNew->truelist = merge(pushend[pushend.size() - 1].truelist, pushend[0].truelist);
pNew->falselist = pushend[0].falselist;
pNew->fristnextquad = pushend[2].fristnextquad;
break;
case 20://B->N
pNew->falselist = pushend[0].falselist;
pNew->truelist = pushend[0].truelist;
pNew->value = pushend[0].value;
break;
case 21://B->not B
pNew->falselist = pushend[0].truelist;
pNew->truelist = pushend[0].falselist;
pNew->value = pushend[0].value;
break;
case 22: //T->T*F
value1 = NewTempStruct();
CreateGen("*", pushend[2].value, pushend[0].value, value1);
pNew->value = value1;
pNew->nextquad = getNextquad();
break;
case 23://T->F
pNew->falselist = pushend[0].falselist;
pNew->truelist = pushend[0].truelist;
pNew->value = pushend[0].value;
break;
case 24://F->(E)
pNew->falselist = pushend[1].falselist;
pNew->truelist = pushend[1].truelist;
pNew->value = pushend[1].value;
break;
case 25://F->id
pNew->value = pushend[0].data.name;
break;
case 26://N->N and M
backpatch(pushend[pushend.size() - 1].truelist, pushend[pushend.size() - 1].nextquad);
pNew->falselist = merge(pushend[pushend.size() - 1].falselist, pushend[0].falselist);
pNew->truelist = pushend[0].truelist;
pNew->fristnextquad = pushend[2].fristnextquad;
break;
case 27:
pNew->falselist = pushend[0].falselist;
pNew->truelist = pushend[0].truelist;
pNew->value = pushend[0].value;
break;
case 28: //M->(B)
pNew->falselist = pushend[1].falselist;
pNew->truelist = pushend[1].truelist;
pNew->value = pushend[1].value;
break;
case 29: //M->id <id
case 30: //>
case 31: //<>
case 32: //<=
case 33://>=
case 34://=
string s1;
int2str(stoi(getNextquad())+ 1, s1);
pNew->truelist.push_back(getNextquad());
pNew->falselist.push_back(s1);
CreateGen("j"+pushend[1].data.name,pushend[2].data.name,pushend[0].data.name,"0");
CreateGen("j","0", "0", "0");
pNew->nextquad = getNextquad();
break;
}
}
其中merge作用是將兩集合合併並返回,backpatch作用是回填,把新值回填到指定四元式集合的第四元素中。CreateGen()產生四元式並加入到四元式集合中,void InsertGentoIndex(string sel,string desti)將四元式中轉移到四元式集合中的指定位置,其中sel代表所在位置,destin代表要轉移到的位置。
注意:在轉移過程中要把在destin和sel之間的所有四元式的label加1,判斷並且跳轉語句的四元式的第四元素是否在在destin和sel之間內,如果在將該四元式的第四元素+1,最後刪除原本的四元式。語義動作表是在規約前完成,找出規約時所用的產生式,根據產生式的索引完成指定的動作。可以根據規約時的終結符集合和產生式中終結符集合是否降等來判斷所用的產生式是哪一條。
7、語法分析:定義(語法)樹的數據結構
因爲語法所用的規約只把終結符推出棧,與非終結符無關。但是這樣就無法進行自下而上的屬性賦值,也就無法完成語義動作。用建立樹的方式,在規約的時候把要規約的符號作爲孩子,而查詢出的規約的產生式左部的非終結符作爲根節點。規約時完成對語法樹的建立偶同時完成語法動作(也就是四元式產生).
struct TreeNode
{
TreeNode()
{
fristnextquad = getNextquad();
nextquad = getNextquad();
value = data.name;
sibling = NULL;
child = NULL;
}
DataType data;
TreeNode * sibling; //右兄弟結點
TreeNode * child; //左子結點
string nextquad; //完成規約時的四元式地址
string fristnextquad; //完成規約前的四元式地址
string value; //代表的值,產生式第四元素如T1,T2
vector<string>nextlist;
vector<string>truelist; //真出口集
vector<string>falselist; //出口集
};
其中getNextquad()爲四元式集合長度,也就是下一條要產生四元式的地址,DataType爲token。完成了數據結構的定義,在規約時,就把每一個要規約的元素建立一個結點(兄弟結點),找到規約對應的產生式,根結點也就是該產生式的左部非終結符。也就是說在語法規約的時候,完成對樹的建立,完成規約時相應的語法動作,最終完成規約,建立好語法樹。乾脆。重新再建立一個符號棧,不過符號棧中元素不在是token而是TreeNode,和原本的符號棧的區別就在於把非終結符也加入到了裏面,而不是與非終結符無關。