📄 by31.cpp
字号:
////////////////////////////////////////////////////////////////
//版权所有:台州学院信息与电子工程学院计算机系 应老师
//制作日期:2003年10月25日
//《编译原理》试验示例
//
//程序功能:
//根据算符优先分析法,将表达式进行语法分析,判断一个表达式是否正确。
//文法: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(TESTIN_FILENAME, "r");
fTestOut = fopen(TESTOUT_FILENAME, "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;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -