編譯原理實驗二:LL(1)語法分析器
一、實驗要求
不得不想吐槽一下編譯原理的實驗代碼量實在是太大了,是編譯原理撐起了我大學四年的代碼量...
這次實驗比上次要復雜得多,涵蓋的功能也更多了,我覺得這次實驗主要的難點有兩個(其實都是難點...):
1. 提取左公因子或消除左遞歸(實現了消除左遞歸)
2. 遞歸求First集和Follow集
其它的只要按照課本上的步驟順序寫下來就好(但是代碼量超多...),下面我貼出實驗的一些關鍵代碼和算法思想。
二、基於預測分析表法的語法分析
2.1 代碼結構
2.1.1 Grammar類
功能:主要用來處理輸入的文法,包括將文法中的終結符和非終結符分別存儲,檢測直接左遞歸和左公因子,消除直接左遞歸,獲得所有非終結符的First集,Follow集以及產生式的Select集。
#ifndef GRAMMAR_H #define GRAMMAR_H #include <string> #include <cstring> #include <iostream> #include <vector> #include <set> #include <iomanip> #include <algorithm> using namespace std; const int maxn = 110; //產生式結構體 struct EXP{ char left; //左部string right; //右部 }; class Grammar { public: Grammar(); //構造函數 bool isNotTer(char x); //判斷是否是終結符 int getTer(char x); //獲取終結符下標 int getNonTer(char x); //獲取非終結符下標 void getFirst(char x); //獲取某個非終結符的First集 void getFollow(char x); //獲取某個非終結符的Follow集void getSelect(char x); //獲取產生式的Select集 void input(); //輸入文法 void scanExp(); //掃描輸入的產生式,檢測是否有左遞歸和左公因子 void remove(); //消除左遞歸 void solve(); //處理文法,獲得所有First集,Follow集以及Select集 void display(); //打印First集,Follow集,Select集 void debug(); //用於debug的函數 ~Grammar(); //析構函數 protected: int cnt; //產生式數目 EXP exp[maxn]; //產生式集合 set<char> First[maxn]; //First集 set<char> Follow[maxn]; //Follow集 set<char> Select[maxn]; //select集 vector<char> ter_copy; //去掉$的終結符 vector<char> ter; //終結符 vector<char> not_ter; //非終結符 }; #endif
2.1.2 AnalyzTable類
功能:得到預測分析表,判斷輸入的文法是否是LL(1)文法,用預測分析表法判斷輸入的符號串是否符合剛才輸入的文法,並打印出分析過程。
#ifndef ANALYZTABLE_H #define ANALYZTABLE_H #include "Gramma.h" class AnalyzTable:public Gramma { public: AnalyzTable(); void getTable(); //得到分析表 void judge(); //判斷是否是LL(1)文法 void analyExp(string s); //分析輸入串 void displayTable(); //打印表 void inputString(); //輸入符號串 ~AnalyzTable(); protected: string s; //符號串 vector<char> stack; //分析棧 vector<char> left; //剩余輸入串 int detect[maxn][maxn]; //檢測表 int table[maxn][maxn]; //預測分析表 }; #endif
2.2 記號和規定
-
-
非終結符:大寫字母‘A‘~‘Z‘
-
終結符:除大寫字母之外的所有字符
-
空串:$
-
符號棧終止符:#
-
規定第一個產生式的左邊那個非終結符就是開始符號
-
輸入的產生式需要分開寫(比如A->a|b, 要輸入A->a和A->b才能處理)
-
2.3 算法思想
2.3.1 求First集的算法思想
-
遍歷每一個左部為x的產生式
-
如果產生式右部第一個字符為終結符,則將其計入左部非終結符的First集
-
如果產生式右部第一個字符為非終結符
-
求該非終結符的First集
-
將該非終結符的去掉$的First集計入左部的First集
-
若存在$,繼續往後遍歷右部
-
若不存在$,則停止遍歷該產生式,進入下一個產生式
-
若已經到達產生式的最右部的非終結符(即右部的First集都含有空串),則將$加入左部的First集
- 處理數組中重復的First集中的終結符
//求出非終結符的First集 void Gramma::getFirst(char x){ cout<<x<<endl; bool flag = 0; //記錄非終結符的First集是否有空串 int tot = 0; //記錄一個非終結符產生式含有空串的產生式 for(int i=0;i<cnt;i++){ if(exp[i].left==x){ //如果右部的第一個字符是終結符 if(!isNotTer(exp[i].right[0])){ First[getNonTer(x)].insert(exp[i].right[0]); } //如果是非終結符 else{ //從左到右遍歷右部 for(int j=0;j<exp[i].right.length();j++){ //如果遇到終結符,結束 if(!isNotTer(exp[i].right[j])){ First[getNonTer(x)].insert(exp[i].right[j]); break; } //不是終結符,求該非終結符的First集 getFirst(exp[i].right[j]); set<char>::iterator it; int ind = getNonTer(exp[i].right[j]); for(it=First[ind].begin();it!=First[ind].end();it++){ if(*it==‘$‘){ flag = 1; }else{ First[getNonTer(x)].insert(*it); } } //沒有空串就不必再找下去了 if(flag==0){ break; }else{ flag = 0; tot++; } } //如果右部所有符號的First集都有空串,則符號x的First集也有空串 if(tot==exp[i].right.length()){ First[getNonTer(x)].insert(‘$‘); } } } } }
2.3.2 求Follow集的算法思想
-
遍歷每一個右部包含非終結符x的產生式
-
如果x的下一個字符是終結符,添加進x的Follow集
-
如果x的下一個字符是非終結符,把該字符的First集加入x的Follow集(不能加入空串)
-
如果下一字符的First集有空串並且該產生式的左部不是x,則把左部的Follow集加入x的Follow集
-
如果x已經是產生式的末尾,則把左部的Follow集添加到x的Follow集裏
//求出非終結符的Follow集 void Gramma::getFollow(char x){ //找到非終結符x出現的位置 for(int i=0;i<cnt;i++){ int index = -1; int len = exp[i].right.length(); for(int j=0;j<len;j++){ if(exp[i].right[j]==x){ index = j; break; } } //如果找到了x,並且它不是最後一個字符 if(index!=-1&&index<len-1){ //如果下一個字符是終結符,添加進x的Follow集 char next = exp[i].right[index+1]; if(!isNotTer(next)){ Follow[getNonTer(x)].insert(next); }else{ //如果下一個字符是非終結符 bool flag = 0; set<char>::iterator it; //遍歷下一個字符的First集 for(it = First[getNonTer(next)].begin();it!=First[getNonTer(next)].end();it++){ if(*it==‘$‘){ flag = 1; }else{ Follow[getNonTer(x)].insert(*it); } } //如果有空串並且左部不是它本身(防止陷入死循環),當前非終結符的Follow集是x的Follow集 char tmp = exp[i].left; if(flag&&tmp!=x){ getFollow(tmp); set<char>::iterator it; for(it = Follow[getNonTer(tmp)].begin();it!=Follow[getNonTer(tmp)].end();it++){ Follow[getNonTer(x)].insert(*it); } } } }else if(index!=-1&&index==len-1&&x!=exp[i].left){ //如果x在產生式的末尾,則產生式左部的Follow集應該添加到x的Follow集裏 char tmp = exp[i].left; getFollow(tmp); set<char>::iterator it; for(it = Follow[getNonTer(tmp)].begin();it!=Follow[getNonTer(tmp)].end();it++){ Follow[getNonTer(x)].insert(*it); } } } }
2.3.3 求預測分析表的算法思想
- 遍歷每一個產生式
-
如果右部的第一個字符tmp是終結符且不是空串,更新預測分析表,即table[left][tmp] = i(i為產生式編號)
-
如果右部的第一個字符是空串,遍歷左部的Follow集,更新預測分析表,即table[left][x] = i(i為產生式編號,x為Follow集字符編號)
-
如果右部的第一個字符是非終結符,遍歷它的First集,更新預測分析表,即table[left][x] = i(i為產生式編號,x為First集字符編號)
//獲得預測分析表 void AnalyzTable::getTable(){ for(int i=0;i<cnt;i++){ char tmp = exp[i].right[0]; //如果產生式右部的第一個字符是終結符 if(!isNotTer(tmp)){ //該終結符不是空串,更新table if(tmp!=‘$‘){ detect[getNonTer(exp[i].left)][getTer(tmp)]++; table[getNonTer(exp[i].left)][getTer(tmp)] = i; } if(tmp==‘$‘){ //該終結符是空串,遍歷左部的Follow集,更新table set<char>::iterator it; for(it = Follow[getNonTer(exp[i].left)].begin();it!=Follow[getNonTer(exp[i].left)].end();it++){ table[getNonTer(exp[i].left)][getTer(*it)] = i; detect[getNonTer(exp[i].left)][getTer(*it)]++; } } }else{ //如果產生式右部的第一個字符是非終結符,遍歷它的First集,更新table set<char>::iterator it; for(it = First[getNonTer(tmp)].begin();it!=First[getNonTer(tmp)].end();it++){ table[getNonTer(exp[i].left)][getTer(*it)] = i; detect[getNonTer(exp[i].left)][getTer(*it)]++; } //如果有空串,遍歷左部的Follow集,更新table if(First[getNonTer(tmp)].count(‘$‘)!=0){ set<char>::iterator it; for(it = Follow[getNonTer(exp[i].left)].begin();it!=Follow[getNonTer(exp[i].left)].end();it++){ table[getNonTer(exp[i].left)][getTer(*it)] = i; detect[getNonTer(exp[i].left)][getTer(*it)]++; } } } } }
2.3.4 符號串的分析過程
- 規定f1為符號棧棧頂字符,f2為剩余輸入串的第一個字符
-
若f1=f2=“#”,則分析成功,停止分析
-
若f1=f2≠”#”,則把f1從棧頂彈出,讓f2指向下一個輸入符號.
-
若f1是一個非終結符,則查看分析表,若table[f1][f2]中有值,則f1出棧,並且產生式的右部反序進棧
-
再把產生式的右部符號推進棧的同時應做這個產生式相應得語義動作,若M[A,a]中存放著”出錯標誌”,則調用出錯診察程序error.
分析棧
流程圖
//分析符號串 void AnalyzTable::analyExp(string s){ cout<<setw(15)<<"分析棧"<<setw(15)<<"剩余輸入串"<<setw(20)<<"推導式"<<endl; //把整個串倒序push進剩余符號vector left.push_back(‘#‘); for(int i=s.length()-1;i>=0;i--){ left.push_back(s[i]); } //把#和開始符push進分析棧 stack.push_back(‘#‘); stack.push_back(not_ter[0]); //如果剩余輸入串長度不為0,就一直循環 while(left.size()>0){ //輸出分析棧內容 string outputs = ""; for(int i=0;i<stack.size();i++){ outputs+=stack[i]; } cout<<setw(15)<<outputs; outputs = ""; //輸出剩余輸入串內容 for(int i=left.size()-1;i>=0;i--){ outputs+=left[i]; } cout<<setw(15)<<outputs; char f1 = stack[stack.size()-1]; char f2 = left[left.size()-1]; //如果可以匹配,並且都為# if(f1==f2&&f1==‘#‘){ cout<<setw(21)<<"Accept!"<<endl; return; } //如果可以匹配,並且都為終結符 if(f1==f2){ stack.pop_back(); left.pop_back(); cout<<setw(15)<<f1<<" 匹配"<<endl; }else if(table[getNonTer(f1)][getTer(f2)]!=-1){ //如果在預測分析表中有值 int index = table[getNonTer(f1)][getTer(f2)]; stack.pop_back(); if(exp[index].right!="$"){ for(int i=exp[index].right.length()-1;i>=0;i--){ stack.push_back(exp[index].right[i]); } } cout<<setw(15)<<exp[index].left<<"->"<<exp[index].right<<endl; }else{ cout<<setw(15)<<"error"<<endl; return; } } }
編譯原理實驗二:LL(1)語法分析器