天天看點

【編譯原理-實驗-2】預測分析表一篇解決你所有問題(c++版)實驗 預測分析表方法

python版本(詞法分析器整合預測分析表):

編譯原理預測分析表一篇解決你所有問題(python版)

實驗 預測分析表方法

一、實驗目的

了解預測分析表方法的實作原理。

二、實驗内容

編寫一通用的預測法分析程式,要求有一定的錯誤處理能力,出錯後能夠使程式繼續運作下去,直到分析過程結束。可通過不同的文法(通過資料表現)進行測試。

三、實驗内容提示

1.算法資料構造:

構造終結符數組:char Vt[10][5]={“id”,”+”……};

構造非終結符數組:char Vn[10]={ };

構造follow集數組:char *follow[10][10]={ } (可将follow集與預測分析表合并存放,可省略,直接給出分析表。)

資料構造示例(使用的預測分析表構造方法1):

/*data1.h簡單算術表達式資料*/
char	VN[10][5]={"E","E'","T","T'","F"}; //非終結符表
int		length_vn=5;   //非終結符的個數

char	VT[15][5]={"id","+","*","(",")","#"};  //終結符表
int		length_vt=6; //終結符的個數

char	Fa[15][10]={"TE'","+TE'","","FT'","*FT'","","(E)","id"};  
//産生式表:E->TE'   1:E'->+TE'  2:E'->空 
//  3:T->FT'  4:T'->*FT'   5:T'->空 6:F->(E)  7:F->id

int		analysis_table[10][11]={0,-1,-1,0,-2,-2,0,0,0,0,0,
							-1,1,-1,-1,2,2,0,0,0,0,0,
							3,-2,-1,3,-2,-2,0,0,0,0,0,
							-1,5, 4,-1,5, 5,0,0,0,0,0,
							7,-2,-2,6,-2,-2,0,0,0,0,0};
//預測分析表,-1表示出錯,-2表示該行終結符的follow集合,用于錯誤處理
           

(1) 預測分析表的構造方法1

給文法的正規式編号:存放在字元數組中,從0開始編号,正規式的編号即為該正規式在數組中對應的下标。

構造正規式數組:char P[10][10]={“E->TE’”,”E’->+TE’”,………}; (正規式可隻存儲右半部分,如E->TE’可存儲為TE’ ,正規式中的符号可替換,如可将E’改為M )

構造預測分析表:int analyze_table[10][10]={ } //數組元素值存放正規式的編号,-1表示出錯

(2)預測分析表的構造方法2

可使用三維數組

Char analyze_table[10][10][10]={ }

Char *analyze_table[10][10][10]={ }

2.針對預測分析表構造方法1的查找方法提示:

(1) 查非終結符表得到非終結符的序号no1

(2) 查終結符表得到終結符的序号no2

(3) 根據no1和no2查正規式表得到對應正規式的序号no3=analyze_table[no1][no2] ,如果no3=-1 表示出錯。

(4) 根據no3查找對應的正規式P[no3]

(5) 對正規式進行處理

3.錯誤處理機制

緊急方式的錯誤恢複方法(抛棄某些符号,繼續向下分析)

(1)棧頂為非終結符A,串中目前單詞屬于FOLLOW(A),則從棧中彈出A(此時可認為輸入串中缺少A表示的結構),繼續分析。 ---------錯誤編号為1

(2)棧頂為非終結符A,串中目前單詞不屬于FOLLOW(A),則可使串指針下移一個位置(認為輸入串中目前單詞多餘),繼續分析。----------錯誤編号為2

(3)棧頂為終結符,且不等于串中目前單詞,則從棧中彈出此終結符(認為輸入串中缺少目前單詞)或者将串指針下移一個位置(認為串中目前單詞多餘)。在程式中可選擇上述兩種 觀點中的一種進行處理。-------------錯誤編号3

是以error()函數的編寫方式可按如下方式處理

Error(int  errornum)
{
If(errornum==1)………………
Else if(errornum==2)……………
Else ………………..
//或者可用choose  case語句處理
}
           

4.增加了錯誤處理的預測分析程式預測分析程式的算法:

将“#”和文法開始符依次壓入棧中;              
把第一個輸入符号讀入a;
do{
把棧頂符号彈出并放入x中;
if(x∈VT)
{
if(x==a)  将下一輸入符号讀入a;
else error(3 );
}
else
if(M[x,a]=“x→y1y2…yk”)
{
按逆序依次把yk、yk−1、…、y1壓入棧中;
輸出“x→y1y2…yk”;
}
else if  afollow(x)error(1 );  else  error(2);
}while(x!=“#”)
           

三.實驗要求

給定算術表達式文法,編寫程式。

測試資料:

1.算術表達式文法

E→TE’
E’ → +TE’|- TE’|ε
T→FT’
T’ →*FT’ |/ FT’ |%FT’|ε
F→(E) |id|num
           

給定一符合該文法的句子,如id+id*id#,運作預測分析程式,給出分析過程和每一步的分析結果。

輸出形式參考下圖:

【編譯原理-實驗-2】預測分析表一篇解決你所有問題(c++版)實驗 預測分析表方法

四、編寫實驗報告

實驗分析:

1.将原算術表達式方法改寫為LL(1)文法為:

E→TE’
E’ → +TE’|- TE’|ε
T→FT’
T’ →*FT’ |/ FT’ |%FT’|ε
F→(E) |id|num
           

2.求出每個非終結符的FIRST和FOLLOW集;

非終結符 FIRET FOLLOW
E { (,id,num } { #,) }
E’ { +,-,ε } { #,) }
T {(,id,num} { +,-,#,) }
T’ { *,/,%,ε } { +,-,#,) }
F { (,id,num } { *,/,%,+,-,# ,)}

3.構造預測分析表

坐标 1 2 3 4 5 6 7 8 9
非\終結符 * / % + - ( ) id num #
0 E error(2) error(2) error(2) error(2) error(2) E→ TE’ error(1) E→ TE’ E→ TE’ error(1)
1 E’ error(2) error(2) error(2) E’ → +TE’ E’ → -TE’ error(2) E’ → ε error(2) error(2) E’ → ε
2 T error(2) error(2) error(2) error(1) error(1) T→FT’ error(1) T→FT’ T→FT’ error(1)
3 T’ T’ →*FT’ T’ →/FT’ T’ →%FT’ T’ →ε T’ →ε error(2) T’ →ε error(2) error(2) T’ →ε
4 F error(1) error(1) error(1) error(1) error(1) F→(E) error(1) F→id F→num error(1)

4.繪制程式設計流程圖

【編譯原理-實驗-2】預測分析表一篇解決你所有問題(c++版)實驗 預測分析表方法

5.c++代碼實作

#include<stdio.h> 
#include<string.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
#define TT 0 
char aa[20]=" ";//用來存儲從txt檔案中讀取的字元串 
int pp=0;//用來标記字元 
						
char VN[5]={'E','e','T','t','F'}; // 非終結符表
int length_vn=5; //非終結符的個數
char VT[10]={'*','l','m','+','-','(',')','i','n','#'}; //終結符表 l->/ m->% i->id n->num
int length_vt=10; // 終結符的個數
char Fa[12][6]={"Te","+Te","-Te","NULL","Ft","*Ft","nFt","mFt","NULL","(E)","i","n"};
//産生式表 :0:E->Te 1:e->+Te 2:e->-Te 3:e->空
char F[12][6]={"E->","E'->","E'->","E'->","T->","T'->","T'->","T'->","T'->","F->","F->","F->"};
//構造預測分析表,-1表示出錯,-2表示該行終結符的follow集合,用于錯誤處理
int analysis_table[5][10]={
-2,-2,-2,-2,-2,0,-1,0,0,-1,
-2,-2,-2,1,2,-2,3,-2,-2,3,
-2,-2,-2,-1,-1,4,-1,4,4,-1,
5,6,7,8,8,-2,8,-2,-2,8,
-1,-1,-1,-1,-1,9,-1,10,11,-1}; 

char stack[50];
int top=-1;

// 程式初始化:輸入并打開源程式檔案
void initscanner() 
{
	int i=0;
	FILE *fp;
	if((fp=fopen("a.txt","r"))==NULL){
		printf("Open error!");
		exit(0);
	}
	char ch=fgetc(fp);
	while(ch!=EOF){
		aa[i]=ch;//将字元依次存入aa數組 
		i++;
		ch=fgetc(fp);
	}
	fclose(fp);
}
//字元入棧 
void push(char a)
{
	top++;
	stack[top]=a;
}
//字元出棧 ,彈出棧頂元素 
char pop()
{
	return stack[top--];
}
//字元x是否是終結符 
int includevt(char x)
{
	for(int i=0;i<length_vt;i++)
	{
		if(VT[i]==x) return 1;
	} 
	return 0;
}
//查找非終結符,終結符 在預測分析表中的坐标,傳回坐标對應資訊 
int includean(char x,char a)
{
	int i,j;
	//非終結符 
	for(i=0;i<length_vn;i++)
		if(VN[i]==x) break;
	//終結符 
	for(j=0;j<length_vt;j++)
		if(VT[j]==a) break;
	
	return analysis_table[i][j];
}

void destory()
{
	int flag=0;
	int flagg=0;
	push('#'); //将 "#"和文法開始符依次壓入棧中
	push(VN[0]);//将第一個非終結符入棧 
	char a =aa[pp]; //把第一個輸入符号讀入 a,aa
	char x;
	//錯誤處理機制
	do{
	//	printf("%s\t\t",stack);
		if(flag==0)
		x=pop(); //把棧頂符号彈出并放入 x 中
		flag=0;
		printf("%c\t\t\t\t%c\t\t",x,a);
		//如果a是終結符 
		if(includevt(a)==1)
		{
			if(includevt(x)==1)
			{
				if(x==a)
				{
					if(a=='#')
					{
						flagg=1;
						printf(" 結束 \n");
					}
					else printf(" 比對終結符 %c\n",x);
					pp++;
					a=aa[pp]; //将下一輸入符号讀入 a;
				}
				else
				{
					flag=1;
					printf(" 出錯 ,跳過 %c\n",a);
					pp++; 
					a=aa[pp];
				}
			}
			//存在該表達式 
			else if(includean(x,a)>=0)
			{
				//擷取分析表對應坐标資料	
				int h=includean(x,a);
				
				printf(" 展開非終結符 %s%s\n",F[h],Fa[h]);
				int k;
				for(k=0;k<10;k++)
					if(Fa[h][k]=='\0') 
					break;
				if(k==4)
				{
					//printf("+++++++++++pop %c \n",x);
				}
				else
				{
					while(k!=0) //按逆序依次把 yk、yk?1、 , 、 y1 壓入棧中
					{
						k--;
						push(Fa[h][k]);
					}
				}
			}
			//-1表示出錯 
			else if(includean(x,a)==-1)
			{
				flag=1;
				printf(" 出錯 ,從棧頂彈出 %c\n",x);
				x=pop();
			}
			// 
			else
			{
				flag=1;
				printf(" 出錯 ,跳過 %c\n",a);
				pp++;
				a=aa[pp];
			}
		}
		else
		{
			flag=1;
			printf(" 出錯 ,跳過 %c\n",a);
			pp++;
			a=aa[pp];
		}
		
	}while(x!='#');
	if(flagg==0)
	{
		printf("%c\t\t\t%c\t",x,a);
		printf(" 結束 \n");
	}
}
int main()
{
	printf(" 文法分析工程如下 :\n");
	initscanner();
	cout<<"-----------------文法如下---------------------"<<endl;
	cout<<"E->TE'"<<endl; 
	cout<<"E'->+TE'|-TE'|~"<<endl; 
	cout<<"T->FT'"<<endl; 
	cout<<"T'->*FT'|/FT'|%FT'|~"<<endl; 
	cout<<"F->(E)|id|num"<<endl; 
	printf(" 要分析的語句是 :%s\n",aa);
	printf(" 文法分析工程如下 :\n");
	printf("棧頂元素 \t\t  目前單詞記号 \t\t 動作 \n");
	printf("--------------------------------------------------------------------\n");
	destory();
	return 0;
}
           

6.測試結果展示

【編譯原理-實驗-2】預測分析表一篇解決你所有問題(c++版)實驗 預測分析表方法

7.不足分析

實驗還存在一定的不足,相信一些同學通過對比測試結果會發現,與實驗要求存在不小的差别,

首先棧頂元素輸出的時候,沒有輸出T‘,取而帶之的是小寫的t,這裡主要差別是前者是兩個字元,後者是一個字元,相比易兩個字元,一個字元在進行字元讀取時更好處理,比如說在倒序入棧時,兩個字元由于可以分割,進入棧中時就變成了’T,這樣就導緻了程式混亂。

還有不同的地方目前單詞記号實驗要求輸出的時id,而我輸出的時i,這裡也是遇到了逆序入棧的問題,多于一個字元串,就會導緻入棧出錯,這裡我想到了解決辦法,這就需要于詞法分析器聯合使用,詞法分析器将單詞分析出來,然後再進行處理。

再然後就是動作動作中由于id是兩個字元,是以在進行處理時,識别了i,但是在識别d是,發現終結符中不存在該字元,導緻程式出錯,識别不出,跳過。這個問題的解決方式也和上一個是一樣的,利用詞法分析,将語句進行詞法分析。再進行處理,就不會出錯了。

實驗算是完成了,但是還有許多不足,下一篇文章将用python再做一遍,python相對于c++更易操作,對字元串的處理更加寬松靈活,好了,做完後我會把python版連接配接分享到這裡。

最後!!!

歡迎點贊,關注

編譯原理預測分析表一篇解決你所有問題(python版)