1. 程式人生 > >編譯原理 實驗3 語法分析

編譯原理 實驗3 語法分析

語法分析

一、 實驗目的

算術表示式的文法可以是(你可以根據需要適當改變):

            E→E+E|E-E|E*E|E/E|(E)|i

根據算符優先分析法,將表示式進行語法分析,判斷一個表示式是否正確。

二、 實驗環境

作業系統:window xp

編寫環境:visual c++ 

編寫語言:c語言

三、 實驗內容

程式輸入/輸出示例:

如參考C語言的運算子。輸入如下表達式(以分號為結束)和輸出結果:

110;

輸出:正確

21+2;

輸出:正確

3(1+2)/3+4-(5+6/7);

輸出:正確

4((1-2)/3+4

輸出:錯誤

51+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;
}