语法分析
一、 实验目的
算术表达式的文法可以是(你可以根据需要适当改变):
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; }
时间: 2024-10-11 01:01:50