实验1 词法分析_羊羊加贝的博客-爱代码爱编程
1.1 实验目的
(1)理解有穷自动机及其应用。
(2)掌握 NFA 到 DFA 的等价变换方法、DFA 最小化的方法。
(3)掌握设计、编码、调试词法分析程序的技术和方法。
1.2 实验任务
选择任务二:
编写一个程序对输入的源代码进行词法分析,并打印分析结果。
自己编写一个 PL/0 词法分析程序,本次实验使用C++语言进行编写。
1.3 实验内容
实验要求:
你的程序要能够查出源代码中可能包含的词法错误:
输入格式:
程序输入是一个包含 PL/0 源代码的文本文件,程序需要能够接收一个输入文件名作为参数,以获得相应的输出结果。
输出格式:
要求输出的信息包括错误类型、出错的行号以及说明文字。
其格式为:
Error type [错误类型] at Line [行号]: [说明文字].
算法描述:
首先把程序中合法的单词放在对应类型的map<string,int>变量里,然后从输入文件中读入程序,第一遍遍历:逐个判断输入的一个字符串是否合法,如果不合法则输出错误接着判断下一个单词,如果合法则判断类型。如果有错误则直接结束程序,如果没有错误则进行第二遍遍历,输出词法判断序列。
程序结构:
主要变量说明:
cc:当前读取的单个字符
ww:判断当前输入程序是否存在词法错误
ss: 存放当前判断的标识符
h: 存储当前行号
gjz:存储当前判断语言的关键字/保留字
ysf:存储当前判断语言的运算符
jf: 存储当前判断语言的界符
程序清单:
函数isl()用来判断当前输入字符是否为字母,通过与‘a’、‘z’、‘A’、‘Z’比较大小来判断并返回结果。
bool isl(char c)//判断字母
{
if(c>='a'&&c<='z')
return 1;
if(c>='A'&&c<='Z')
return 1;
return 0;
}
函数ism()用来判断当前输入字符是否为数字,通过与‘0’、‘9’比较大小来判断并返回结果。
bool ism(char c)//判断数字
{
if(c>='0'&&c<='9')
return 1;
return 0;
}
函数isf()用来判断当前输入字符是否为运算符或者界符,通过与搜索map中当前搜索的字符出现次数,未出现则返回0,以此来判断并返回结果。
int isf(string c)//判断字符
{
//运算符
if(ysf.count(c))
{
return 1;
}
//界符
if(jf.count(c))
{
return 2;
}
return 0;
}
函数analysis()用来对输入文件进行词法分析,依次读入当前程序的字符,
首先判断当前程序字符是否读完,以及将空格换行等格式符号跳过;
再判断当前输入符号是否为字母,如果当前字符为字母则接收整个字符串直到空格出现或者运算符和界符出现,在此过程中对每个字符进行检查,如果出现程序中不应该出现的符号则报错提示不合法输入。
接收完字符串后判断当前字符串是否为保留字,如果不是保留字则为标识符。
如果当前字符为数字,则继续接收整个字符串,当空格出现或者运算符和界符出现停止接收,在此过程中对每个字符进行检查,如果出现程序中不应该出现的符号则报错提示不合法输入,如果接收的字符串全为数字,则当前字符串为常数。
如果当前字符为运算符或者界符,则继续判断下一个字符是否为运算符或界符,因为有的运算符由两个字符组成,在此过程中对每个字符进行检查,如果出现程序中不应该出现的符号则报错提示不合法输入,如果接收的字符串能在map类型的jf中找到,则当前字符串为运算符或界符。
当前字符指向下一个字符,重复上述判断步骤。
int analysis(FILE* fl)
{
do
{
cc = fgetc(fl);
if( feof(fl) ) // 读完
{
break ;
}
if(cc==' '|| cc=='\t')//格式符号,不处理
{
continue;
}
if(cc=='\n') //换行加一
{
h++;
}
else
{
if(isl(cc))
//字母开头 保留字 标识符 非法字符
{
string s="";
s+=cc;
//接收整个字符
while(1){
char ls=' ';
ls=fgetc(fl);
if(ls==' '|| ls=='\t'||ls=='\n'||feof(fl))
{
if(ls=='\n')
h++;
break;
}
string lss="";
lss+=ls;
if(isf(lss))
//符号有可能紧挨着标识符等出现
{
//回退一个指针
fseek(fl, -1L, SEEK_CUR);
break;
}
if(ism(ls)==0&&isl(ls)==0&&isf(lss)==0)
//不合法输入
{
cc=ls;
ww=1;
cout<<"Error type A at Line ";
cout<<h;
cout<<": Mysterious character ";
cout<<cc;
cout<<"."<<endl;
}
s+=ls;
}
//判断是否为关键字
if(gjz.count(s)&&ww==2)
{
cout<<"保留字:"<<s<<endl;
}
else if(ww==2){
cout<<"标识符:"<<s<<endl;
}
}
else if (ism(cc))
//数字开头 只能是纯数字 其他不合法
{
bool zm=0;
string s="";
s+=cc;
//接收整个字符
while(1){
char ls=' ';
ls=fgetc(fl);
if(ls==' '|| ls=='\t'||ls=='\n'||feof(fl))
{
if(ls=='\n')
h++;
break;
}
string lss="";
lss+=ls;
if(isf(lss))
//符号有可能紧挨着标识符等出现
{
if(lss!=".")
{
//回退一个指针
fseek(fl, -1L, SEEK_CUR);
break;
}
}
if(ism(ls)==0&&isl(ls)==0&&isf(lss)==0)
//不合法输入
{
cc=ls;
ww=1;
cout<<"Error type A at Line ";
cout<<h;
cout<<": Mysterious character ";
cout<<cc;
cout<<"."<<endl;
}
if(isl(ls))
{
zm=1;
}
s+=ls;
}
if(zm)
{
ss=s;
ww=1;
//标识符不合法
cout<<"Error type A at Line ";
cout<<h;
cout<<": Mysterious character ";
cout<<ss;
cout<<"."<<endl;
}
else if(ww==2){
cout<<"常数:"<<s<<endl;
}
/* code */
}
else{
string lss="";
lss+=cc;
if (isf(lss)) //字符处理
{
if(lss==":")
{
char ls=' ';
ls=fgetc(fl);
if(ls==' '|| ls=='\t'||ls=='\n'||feof(fl))
{
if(ls=='\n')
h++;
break;
}
if(ism(ls)==0&&isl(ls)==0&&isf(lss)==0)
//不合法输入
{
cc=ls;
ww=1;
cout<<"Error type A at Line ";
cout<<h;
cout<<": Mysterious character ";
cout<<cc;
cout<<"."<<endl;
}
if(ls=='=')
{
if(ww==2)
{
cout<<"运算符:"<<":="<<endl;
}
}
else{
cc=ls;
//回退一个指针
fseek(fl, -1L, SEEK_CUR);
cout<<"Error type A at Line ";
cout<<h;
cout<<": Mysterious character ";
cout<<cc;
cout<<"."<<endl;
}
}
else if(lss==">")
{
char ls=' ';
ls=fgetc(fl);
if(ls==' '|| ls=='\t'||ls=='\n'||feof(fl))
{
if(ls=='\n')
h++;
break;
}
if(ls=='='&&ww==2)
{
cout<<"运算符:"<<">="<<endl;
}
else if(ww==2){
cout<<"运算符:"<<">"<<endl;
}
}
else if(lss=="<")
{
char ls=' ';
ls=fgetc(fl);
if(ls==' '|| ls=='\t'||ls=='\n'||feof(fl))
{
if(ls=='\n')
h++;
break;
}
if(ls=='='&&ww==2)
{
cout<<"运算符:"<<"<="<<endl;
}
else if(ww==2){
cout<<"运算符:"<<"<"<<endl;
}
}
else{
if(isf(lss)==1&&ww==2)
{
cout<<"运算符:"<<lss<<endl;
}
else if(ww==2){
cout<<"界符:"<<lss<<endl;
}
}
}
else{
ww=1;
cout<<"Error type A at Line ";
cout<<h;
cout<<": Mysterious character ";
cout<<cc;
cout<<"."<<endl;
}
}
}
}while(1);
return 2;
};
主函数main()实现以下功能:
将相应保留字,运算符,界符存入相应的map中。
提示用户输入文件,并接收文件,判断文件是否可以正常打开。
调用词法分析函数进行判断,此次判断主要目的是检查输入程序中是否有错误,有则输出错误信息。
如果没有错误信息则进行第二次遍历,输出词法分析序列。
最后,关闭打开文件,结束程序。
int main()
{
//PL/0 保留字
gjz["const"]=1;gjz["var"]=1;gjz["procedur"]=1;gjz["begin"]=1;gjz["end"]=1;
gjz["odd"]=1;gjz["if"]=1;gjz["then"]=1;gjz["call"]=1;gjz["while"]=1;
gjz["do"]=1;gjz["read"]=1;gjz["write"]=1;
//PL/0 运算符
ysf["+"]=1;ysf["-"]=1;ysf["*"]=1;ysf["/"]=1;ysf["<"]=1;
ysf["<="]=1;ysf[">"]=1;ysf[">="]=1;ysf["#"]=1;ysf["="]=1;ysf[":"]=1;
//PL/0 界符
jf["("]=1;jf[")"]=1;jf[","]=1;jf[";"]=1;jf["."]=1;
char input[30];
FILE* flin;
cout<<"请输入源文件名:"<<endl;
for(;;)
{
cin>>input;
if ((flin = fopen(input, "r")) != NULL) //打开文件
break;
else
cout << "路径输入错误" << endl;
}
cout<<"词法分析结果:"<<endl;
int jg=analysis(flin);
flin = fopen(input, "r");
if(!ww)
{
ww=2;
jg=analysis(flin);
}
fclose(flin);//关闭文件
return 0;
}
调试情况及各种情况运行结果截图:
结果展示:
- 输入程序没有错误的案例:
- 输入程序有一个错误的案例:
- 输入程序有多个错误的案例:
1.4 心得体会
1.5 参考资料
完整版资源中有