編譯原理 實驗3 語法分析
阿新 • • 發佈:2018-12-22
語法分析
一、 實驗目的
算術表示式的文法可以是(你可以根據需要適當改變):
E→E+E|E-E|E*E|E/E|(E)|i
根據算符優先分析法,將表示式進行語法分析,判斷一個表示式是否正確。
二、 實驗環境
作業系統:window xp
編寫環境:visual c++
編寫語言:c語言
三、 實驗內容
程式輸入/輸出示例:
如參考C語言的運算子。輸入如下表達式(以分號為結束)和輸出結果:
(1)10;
輸出:正確
(2)1+2;
輸出:正確
(3)(1+2)/3+4-(5+6/7);
輸出:正確
(4)((1-2)/3+4
輸出:錯誤
(5)1+2-3+(*4/5)
輸出:錯誤
實驗步驟:
1.簡述你的程式實現的功能是什麼?
判斷一個表示式是否遵循給的文法
E→E+E|E-E|E*E|E/E|(E)|i
2.程式的模組描述。
字串ch是讀取txt檔案的一個表示式 每個表示式以分號分割
字串ch2是將讀取的表示式轉化 變成以#結尾的符號串 如10*(2+3)變成i*(i*i)#
St是符號棧棧 以#開始
Table是算符優先表
四、 實驗結果
五、 實驗小結
#include <cstdio> #include <cstring> using namespace std; const int maxn = 1000; FILE * in; FILE * out; char st[maxn];//符號棧 char ch[maxn], ch2[maxn];//符號串 int top, id; int len, n;//符號串長度 char map[6][4] = {"E+E","E-E","E*E","E/E","(E)","i"}; //文法產生式右部 char msg[1000];//錯誤資訊 char table[8][8] = { {'>','>','<','<','<','>','<','>'}, {'>','>','<','<','<','>','<','>'}, {'>','>','>','>','<','>','<','>'}, {'>','>','>','>','<','>','<','>'}, {'<','<','<','<','<','=','<','-'}, {'>','>','>','>','-','>','-','>'}, {'>','>','>','>','-','>','-','>'}, {'<','<','<','<','<','-','<','='} };//優先關係表:八個字元分別是+-*/()i#,其中'-'表示出錯 void init() { in = fopen("f:\\f3.txt", "r"); out = fopen("f:\\fd.txt", "w"); } void end() { fclose(in); fclose(out); } bool get_one()//得到一個符號串 { char c; len = 0; int flag = 0; while(1) { if((c = fgetc(in)) != EOF) { if(c == ';') { //c[len++] = '#'; break; } ch[len++] = c; } else//檔案結束 { flag = 1; break; } } if(len) { ch[len] = 0; //puts(ch); return true; } if(!flag) return true; return false; } bool get() { n = 0; for(int i = 0; i < len; i++) { if(ch[i] == '+' || ch[i] == '-' || ch[i] == '*' || ch[i] == '/') ch2[n++] = ch[i]; else if(ch[i] == '(' || ch[i] == ')') ch2[n++] = ch[i]; else if(ch[i] >= '0' && ch[i] <= '9') { while(i < len && ch[i] >= '0' && ch[i] <= '9') i++; i--; ch2[n++] = 'i'; } else if(ch[i] == 10 || ch[i] == 9 || ch[i] == 32) continue; else return false; } ch2[n++] = '#'; ch2[n] = 0; puts(ch2); return true; } int ID(char c) { if(c == '+') return 0; if(c == '-') return 1; if(c == '*') return 2; if(c == '/') return 3; if(c == '(') return 4; if(c == ')') return 5; if(c == 'i') return 6; if(c == '#') return 7; } int get_pos(int p) { while(st[p] == 'E') p--; return p; } bool gy(int p) { //printf("----%d\n", p); for(int i = 0; i < 6; i++) { int flag = 0, j, k; for(j = p, k = 0; j < top && map[i][k]; j++) { if(map[i][k++] != st[j]) { flag = 1; break; } } if(!flag && map[i][k] == 0) { top = p; st[top++] = 'E'; return true; } } return false; } int guiyue() { int p = get_pos(top-1); int q = p; while(1) { q = p; p = get_pos(p-1); char c1 = st[p], c2 = st[q]; char r = table[ID(c1)][ID(c2)]; //printf("%d %d %c\n", p, q, r); if(r == '<') break; else if(r == '=') continue; else return -1; } int tmp = gy(p+1); //printf("+++++++%d\n", tmp); if(tmp == 0) return tmp; else { //for(int i = 0; i < top; i++) //printf("%c", st[i]); //puts(""); if(top == 2 && st[0] == '#' && st[1] == 'E' && ch2[id] == '#') return 1; return 2; } } int movein() { char p = st[get_pos(top-1)]; char q = ch2[id]; char r = table[ID(p)][ID(q)]; //printf("****%c******\n", r); if(q == '#') return 1; if(r == '<' || r == '=')//需要移進 { st[top++] = q; id++; return 2; } else if(r == '>') return 1; else return 0; } bool ok() { top = 0;//棧指標 id = 0;//符號串指標 st[top++] = '#'; while(true) { int tmp = movein(); //printf("******%d\n", tmp); if(tmp == 1)//需要規約 { int ans = guiyue(); if(ans == 1)//規約完成 return true; else if(ans == 2) continue; else { strcpy(msg, "規約錯誤"); return false; } } else if(tmp == 2)// 繼續 continue; else { strcpy(msg, "移進錯誤"); return false; } } } int main() { init(); while(get_one()) { get(); if(n == 1) continue; if(ok()) strcpy(msg, "正確"); fprintf(out, "%s\n%s\n", ch, msg); } end(); return 0; }
參考程式碼//《編譯原理》實驗示例 // //程式功能: //根據算符優先分析法,將表示式進行語法分析,判斷一個表示式是否正確。 //文法:E→E+E|E-E|E*E|E/E|(E)|i // 其中i為無符號整數 // //例: //輸入:10; //輸出:正確 //輸入:1+2; //輸出:正確 //輸入:(1+2)/3+4-(5+6/7); //輸出:正確 //輸入:((1-2)/3+4; //輸出:錯誤 // //輸入測試資料儲存在同目錄的文字檔案testin.txt中,儲存格式: // 表示式行; // 表示式行; // ..... //預期的輸出儲存在同目錄的文字檔案testout.txt中,儲存格式: // 表示式行; // 正確/錯誤 // 表示式行; // 正確/錯誤 // ..... ///////////////////////////////////////////////////////////////// #include "stdio.h" #include "stdlib.h" #define TRUE 1 #define FALSE 0 //檔案資訊: #define TESTIN_FILENAME "testin.txt" #define TESTOUT_FILENAME "testout.txt" FILE * fTestIn; FILE * fTestOut; //開啟檔案後的柄 //運算子定義: #define O_NUMBER 8 //運算子個數,+-*/()i# #define O_PLUS 0 // 加+ #define O_MINUS 1 // 減- #define O_TIMES 2 // 乘* #define O_SLASH 3 // 除/ #define O_L_PAREN 4 //左括號(parenthesis) #define O_R_PAREN 5 //右括號 #define O_IDENT 6 //識別符號 #define O_NUL 7 //語法界符# //表示式緩衝區:由專門函式操作(ReadFormula(),GetChar()) #define BUFFER_SIZE 1000 //表示式緩衝區大小 char Buffer[BUFFER_SIZE]; //表示式緩衝區,以'\0'表示結束 int ipBuffer = 0; //表示式緩衝區當前位置序號 //算符優先關係表: char O_Table[O_NUMBER][O_NUMBER] = { {'>','>','<','<','<','>','<','>'}, {'>','>','<','<','<','>','<','>'}, {'>','>','>','>','<','>','<','>'}, {'>','>','>','>','<','>','<','>'}, {'<','<','<','<','<','=','<','-'}, {'>','>','>','>','-','>','-','>'}, {'>','>','>','>','-','>','-','>'}, {'<','<','<','<','<','-','<','='} }; //優先關係表:八個字元分別是+-*/()i#,其中'-'表示出錯 //文法: #define OG_NUMBER 6 //文法產生式個數 char OG[OG_NUMBER][4] = {"E+E","E-E","E*E","E/E","(E)","i"}; //文法產生式右部 //單詞序列存放格式定義: #define TOKEN_MAX_LENTH 100 //最大的單詞長度+1 typedef struct { char ch; //存放字元:+-*/()i#E int No; //存放算符優先關係表中的序號 //double Value; //當ch==i時,且為數值時,存放值的大小 } SToken; #define MAX_TOKEN_NUMBER 1000 //在一個表示式中允許最大的單詞個數 SToken Token[MAX_TOKEN_NUMBER]; //單詞序列,最後一個以“#”結束 int TokenNumber = 0; //單詞序列中包含的單詞個數 int ipToken = 0; //進行“移進-規約”時的位置指示 //堆疊:由專門的函式操作(PopUp(),Push(),…) #define STACK_MAX_SIZE 1000 //堆疊最大儲存量 SToken Stack[STACK_MAX_SIZE]; //堆疊 int ipStack = 0; //堆疊指標,指向棧頂(下一個空位置) //詞法分析專用全域性變數: char ch; //存放取得的一個字元 //char AToken[TOKEN_MAX_LENTH]; //存放組成的單詞,存放時以\0為結束 //int ipAToken; //用於讀字元時,指向下一個AToken[]的位置,便於組成單詞 //錯誤資訊: char * ErrMsg; //出錯資訊 //函式宣告: bool Judge(); //利用算符優先關係表判斷單詞序列是否正確 int GuiYue(); //規約,並判斷是否完成 bool IsOK(); //判斷規約是否全部完成 bool GuiYueN(int n); //將堆疊中0~n單詞規約 int FindPriorOp(int Begin); //在堆疊中,從Begin開始,查詢前一個終結符位置 int MoveIn(); //移進,並判斷是否需要規約 void JudgeInit(); //(利用算符優先關係表判斷單詞序列是否正確)判斷前的初始化 SToken Peek(int n); //窺視堆疊 bool PopUp(int n); //彈出堆疊 void PushToken(char ch, int O_No); //壓棧(以字元形式) void Push(SToken Token); //壓棧 bool Init(); //全域性初始化 void End(); //程式退出前作善後處理 void OutPut(char * Formula, char * Result); //將結果輸出到檔案 bool ReadFormula(); //從檔案中讀出一個表示式存於表示式緩衝區Buffer[]中,以'\0'結束,並置ipBuffer=0; bool ChangeToTokens(); //將表示式分割成單詞序列 char GetFirstChar(); //從表示式緩衝區中取到下面第一個非空字元 char GetChar(); //從表示式緩衝區取一個字元,返回該字元的同時將它存於全域性變數ch中 bool MakeErr(char * ErrMassage); //生成錯誤資訊,錯誤資訊存於全域性變數ErrMsg中 /////////////////////////////////////// void main() { if(! Init()) //初始化 { printf("初始化失敗!程式不能繼續。錯誤資訊如下:\n%s\n",ErrMsg); exit(0); } while(ReadFormula()) //從檔案中讀表示式成功 { if(ChangeToTokens()) //將表示式分割成單詞序列 { if(Judge()) //利用算符優先關係表判斷表示式(單詞序列)是否正確 OutPut(Buffer,"正確!"); else OutPut(Buffer,ErrMsg); //輸出錯誤資訊 } else //出錯 { OutPut(Buffer,ErrMsg); //輸出錯誤資訊 } } End(); //程式退出前作善後處理 } //利用算符優先關係表判斷單詞序列是否正確 //返回:TRUE正確;FALSE錯誤,且錯誤資訊存於ErrMsg //本函式的實現思路: // 將單詞序列進行“移進-規約”操作,最後判斷是否能全部完成 // 使用到:堆疊(SToken Stack[])、文法(char OG[][])、算符優先關係表(char O_Table[][])等 bool Judge() { JudgeInit(); PushToken('#',O_NUL); //將“#”號置棧底 while(TRUE) //進行“移進-規約”操作 { switch(MoveIn()) { case 1: //需要規約 switch(GuiYue())//規約 { case 1: //這一步規約成功 break; case 2: //規約全部完成 return TRUE; default: //出錯 ErrMsg = "規約錯誤。"; return FALSE; } break; case 2: //需要繼續移進 break; default: //出錯 return FALSE; } } } //規約,並判斷是否完成 //返回:-1出錯,1這一步規約成功,2規約全部完成 int GuiYue() { int n0,n; char r; //存優先關係 n = FindPriorOp(-1); //取得堆疊中第一個終結符 if(Peek(n).ch == '#') //出錯或全部結束 { if(IsOK()) return 2; else return -1; } while(TRUE) { n0 = n; n = FindPriorOp(n0); //前一個終結符的堆疊位置 if(n - n0 > 2) //出錯(多個非終結符相鄰) return -1; r = O_Table[Peek(n).No][Peek(n0).No]; if(r == '<') //尋找結束 { if(! GuiYueN(n - 1)) //規約(從前一個後的字元開始)規約失敗 return -1; else //規約成功,還要判斷是否全部完成 { if(IsOK()) return 2; //規約全部完成 else return 1; //這一步規約成功 } } else if(r == '=') //繼續向前找 { continue; } else //出錯(r為>或沒有關係) return -1; } } //判斷規約是否全部完成 //返回:TRUE全部完成;FALSE沒有完成 bool IsOK() { //if(Peek(1) == NULL) return FALSE; if(Peek(0).ch == 'E'&& Peek(1).ch == '#' && Token[ipToken].ch == '#') return TRUE; else return FALSE; } //返回:TRUE成功,FALSE失敗 bool GuiYueN(int n) //將堆疊中0~n單詞規約 { int i,j; bool k; for(i=0;i<OG_NUMBER;i++) //將規約串和文法右部OG[][]每一個進行比較 { for(j=n,k=FALSE;j>=0;j--) { if(OG[i][n-j] != Peek(j).ch) { k = TRUE; //TRUE表示規約串和文法右部不符, break; } } if(k) continue; //k==FALSE表示規約串判斷完成 if(OG[i][n+1]=='\0') //文法也判斷完成,匹配成功 { PopUp(n + 1); //彈出規約串 PushToken('E',O_IDENT);//壓入左部“E” return TRUE; } } return FALSE; } //在堆疊中,從Begin開始,查詢前一個終結符位置 //如果從開始找,讓 Begin = -1 int FindPriorOp(int Begin) { int n; n = Begin + 1; while(Peek(n).ch == 'E') { n ++; } return n; } //移進,並判斷是否需要規約 //返回:-1出錯,1需要規約,2可繼續移進 // 1.單詞結束(遇到“#”號),無法移進,需要規約,返回:1 // 2.單詞沒有結束,需判斷是否可以移進 // 2-1.堆疊單詞<=單詞:移進後返回:2 // 2-2.堆疊單詞>單詞:不能移進,需要規約,返回:1 // 2-3.兩單詞沒有優先關係:出錯,返回:-1 int MoveIn() { SToken s,t; //分別存堆疊頂單詞和單詞序列的第一個單詞 char r; //存放優先關係 s = Peek(FindPriorOp(-1)); //取得堆疊中第一個終結符位置 t = Token[ipToken]; r = O_Table[s.No][t.No]; if(t.ch == '#') //單詞結束,無法移進,需要規約 return 1; else //單詞沒有結束,需判斷是否可以移進 { if(r == '<' || r == '=') //需要移進 { Push(t); ipToken ++; return 2; } else if(r == '>') //不能移進,需要規約 return 1; else //沒有優先關係,出錯 { MakeErr("移進時出現兩個沒有優先關係的相鄰單詞。"); return -1; } } } //(利用算符優先關係表判斷單詞序列是否正確)判斷前的初始化 //由於多個表示式需要依次判斷,因此對每個表示式判斷前都需要初始化 void JudgeInit() { ipStack = 0; //堆疊初始化(如果有專門的StackClear()函式則更好) ipToken = 0; //指向首個單詞 } //窺視堆疊 //引數:n相對棧頂的位置(0開始) //成功返回:返回單詞 //不成功返回:NULL SToken Peek(int n) { SToken Token; if(n > 0 || n < ipStack) Token = Stack[ipStack - n - 1]; else if(n < 0) Token = Stack[ipStack - 1]; else Token = Stack[0]; return Token; } //彈出堆疊 //引數:n彈出單詞個數(不能全部彈空,即保留#號) //不成功返回:FALSE //成功返回:TRUE bool PopUp(int n) { if(ipStack < 2) return FALSE; //只剩0個或1個 if(n > ipStack - 1) n = ipStack - 1; ipStack -= n; return TRUE; } //壓棧(以字元形式) //引數:ch是要壓棧的字元(+-*/()i#E 之一),O_No運算子序號 //呼叫:Push() void PushToken(char ch, int O_No) { SToken Token; Token.ch = ch; Token.No = O_No; Push(Token); } //壓棧 //引數:Token是要壓棧的SToken結構體型別的單詞 //缺點:沒有判斷堆疊是否滿 void Push(SToken Token) { Stack[ipStack ++] = Token; } //全域性初始化 //成功:返回TRUE;失敗:返回FALSE bool Init() { //if((fTestIn = fopen(TESTIN_FILENAME, "r")) = NULL) return ! MakeErr("不能開啟測試檔案!"); //if((fTestOut = fopen(TESTOUT_FILENAME, "w")) = NULL) return ! MakeErr("不能開啟結果輸出檔案!"); fTestIn = fopen("d:\\fd3.txt", "r"); fTestOut = fopen("d:\\fd.txt", "w"); return TRUE; } //程式退出前作善後處理 //主要是關閉檔案等 void End() { fclose(fTestIn); fclose(fTestOut); } //將結果輸出到檔案 //要求檔案事先以追加方式開啟,檔案指標為fTestOut //引數:Formula表示式內容,Result判斷結果 void OutPut(char * Formula, char * Result) { fprintf(fTestOut,"%s\n%s\n",Formula,Result); } //從檔案中讀出一個表示式存於表示式緩衝區Buffer[]中,以'\0'結束,並置ipBuffer=0; //需要先開啟檔案,檔案指標存於fTestIn //讀出非空表示式:返回 TRUE;檔案結束:返回 FALSE bool ReadFormula() { int n = 0; bool k = FALSE; //當 k==TRUE 時表示檔案結束,否則檔案沒有結束 while(TRUE) { if((Buffer[n] = fgetc(fTestIn)) != EOF) //讀出一個字元成功 { if(Buffer[n] == ';') break; n ++; } else //檔案結束 { k = TRUE; break; } } Buffer[n] = '\0'; //最後一個字元用結束標記'\0'代替 ipBuffer = 0; //初始化緩衝區指標 if(n > 0) //讀出的資料非空,返回成功 return TRUE; else //讀出的資料為空,需要判斷檔案結束,還是隻有';'的空表示式 { if(k) //檔案結束 return FALSE; else //空表示式,檔案沒有結束,讓它繼續讀下一個表示式 return ReadFormula(); } } //將表示式分割成單詞序列 //結果:單詞序列存於SToken Token[]中,單詞個數存於TokenNumber中 //這是一個大模組,其中要呼叫一些子函式 //本函式只識別:運算子+-*/、括號()、無符號整數i,並在末尾新增#號 // 遇到其它任何字元都返回錯誤資訊 //返回:TRUE表示成功;FALSE表示失敗,同時將錯誤資訊存於全域性變數ErrMsg中 //使用到的其他全域性變數:ch(取一個字元)、AToken[](取到的單詞) bool ChangeToTokens() { TokenNumber = 0; if(GetFirstChar() == '\0') return ! MakeErr("表示式為空。"); while(TRUE) //對緩衝區進行迴圈讀 { if(ch <= 32 && ch > 0) GetFirstChar(); //濾去空格 switch(ch) //對單詞的第一個進行判斷,在下面一次處理整個單詞 { case '\0': Token[TokenNumber].ch = '#'; Token[TokenNumber].No = O_NUL; return TRUE; //處理結束 case '+': Token[TokenNumber].ch = '+'; Token[TokenNumber].No = O_PLUS; GetChar(); break; case '-': Token[TokenNumber].ch = '-'; Token[TokenNumber].No = O_MINUS; GetChar(); break; case '*': Token[TokenNumber].ch = '*'; Token[TokenNumber].No = O_TIMES; GetChar(); break; case '/': Token[TokenNumber].ch = '/'; Token[TokenNumber].No = O_SLASH; GetChar(); break; case '(': Token[TokenNumber].ch = '('; Token[TokenNumber].No = O_L_PAREN; GetChar(); break; case ')': Token[TokenNumber].ch = ')'; Token[TokenNumber].No = O_R_PAREN; GetChar(); break; default: if(ch >= '0' && ch <= '9') //整數 { while(GetChar()>0) { if(ch < '0' || ch > '9') break; } Token[TokenNumber].ch = 'i'; Token[TokenNumber].No = O_IDENT; } else { return ! MakeErr("表示式中含有非法字元。"); } break; } TokenNumber ++; } } //從表示式緩衝區中取到下面第一個非空字元 //成功:返回字元;不成功:返回'\0' char GetFirstChar() { while(GetChar() != '\0') { if(ch>32) return ch; } return '\0'; } //從表示式緩衝區取一個字元,返回該字元的同時將它存於全域性變數ch中 //成功:返回字元;不成功:返回'\0' char GetChar() { if((ch = Buffer[ipBuffer]) != '\0') ipBuffer ++; return ch; } //生成錯誤資訊 //錯誤資訊存於全域性變數ErrMsg中 //返回:TRUE bool MakeErr(char * ErrMassage) { ErrMsg = ErrMassage; return TRUE; }