实验目的
学习和掌握词法分析程序手工构造状态图及其代码实现方法。
实验任务
- 阅读已有编译器的经典词法分析源程序;
- 用C或C++语言编写一门语言的词法分析器。
实验内容
- 阅读已有编译器的经典词法分析源程序。
选择一个编译器,如:TINY,其它编译器也可(需自备源代码)。阅读词法分析源程序,理解词法分析程序的手工构造方法——状态图代码化。尤其要求对相关函数与重要变量的作用与功能进行稍微详细的描述。若能加上学习心得则更好。TINY语言请参考《编译原理及实践》第2.5节(见压缩包里附带的文档)。
- 确定今后其他实验中要设计编译器的语言,如TINY语言,又如更复杂的C-语言(其定义在《编译原理及实践》附录A中)。也可选择其它语言,不过要有该语言的详细定义(可仿照C-语言)。一旦选定,不能更改,因为要在以后继续实现编译器的其它部分。鼓励自己定义一门语言。
- 根据该语言的关键词和识别的词法单元以及注释等,确定关键字表,画出所有词法单元和注释对应的DFA图。
- 仿照前面学习的词法分析器,编写选定语言的词法分析器。
- 准备2~3个测试用例,要求包含正例和反例,测试编译结果。
提示
在充分理解状态转换图代码化思想的基础上,思考不同的程序设计语言从词法角度有什么区别,可利用增量编程的思想提高编程效率。实验通过测试后,按规定时间上交源代码、测试样例、输出文件(如有输出文件)和电子版实验报告。
状态转换图
代码实现
#include<iostream>
#include<fstream>
#include<string.h>
#include<assert.h>
using namespace std;
int LINE=1; //用于记录代码的行数
bool flag=false; //用于表示当前注释是否结束
typedef enum
{
ENDFILE,ERROR, //文件读取完毕和错误
IF,ELSE,INT,RETURN,VOID,WHILE, //六关键字
ID,NUM, //数字和字母
ADD,SUB,MUL,DIV,L,LE,G,GE,ISE,NT,NTE,FZ,FH,DH,LXK,RXK,LZK,RZK,LDK,RDK,LZS,RZS // + - * / < <= > >= == != = ; , ( ) [ ] { } /* */
}TokenType;
typedef enum
{
START,INNUM,INID,INLT,INGT,INEQ,INNOT,INCOMMENT,INDELETE,READDELETE,DONE
//开始、数字、字符、小于等于、大于等于、赋值或等于、注释开始,注释内容开始,注释内容结束,完成和单个字符直接完成
}TokenState;
bool isnum(char a) //检测当前字符是否为数字
{
if(a>='0'&&a<='9')
return true;
return false;
}
bool isalpha(char a) //检测当前字符是否为字母
{
if((a>='a'&&a<='z')||(a>='A'&&a<='Z'))
return true;
return false;
}
void PrintToken(TokenType token,const string TokenString) //对于每一种状态输出每一样的词法分类结果
{
if(TokenString!="\0") //如果是空行,那么跳过,保持格式
cout<<"\t"<<LINE<<":";
switch(token) //根据当前状态输出应当匹配的东西
{
case IF:
case INT:
case ELSE:
case RETURN:
case VOID:
case WHILE:
cout<<"reserved word:"<<TokenString<<endl; //六个预留词都是单独的状态
break;
case ADD: printf("+\n"); break;
case SUB: printf("-\n"); break;
case MUL: printf("*\n"); break;
case DIV: printf("/\n"); break;
case L: printf("<\n"); break;
case LE: printf("<=\n"); break;
case G: printf(">\n"); break;
case GE: printf(">=\n"); break;
case ISE: printf("==\n"); break;
case NT: printf("!\n"); break;
case NTE: printf("!=\n"); break;
case FZ: printf("=\n"); break;
case FH: printf(";\n"); break;
case DH: printf(",\n"); break;
case LXK: printf("(\n"); break;
case RXK: printf(")\n"); break;
case LZK: printf("[\n"); break;
case RZK: printf("]\n"); break;
case LDK: printf("{\n"); break;
case RDK: printf("}\n"); break;
case ENDFILE: if(TokenString!="\0") cout<<"COMMENT:"<<TokenString<<endl;break; //ENDFILE作为注释结束的输出,直接输出整个字符串
case NUM:
cout<<"NUM, val="<<TokenString<<endl; break;
case ID:
cout<<"ID, name="<<TokenString<<endl; break;
case ERROR: //如果输入符合文法,那么不会出现这种状态
cout<<"ERROR:"<<TokenString<<endl; break;
}
}
TokenType Find(string a) //预留词也是特殊的ID,因此当匹配到ID时,应当检测它有没有可能是预留词
{
if(a=="if") return IF;
else if(a=="else") return ELSE;
else if(a=="int") return INT;
else if(a=="return") return RETURN;
else if(a=="void") return VOID;
else if(a=="while") return WHILE;
else return ID;
}
void getToken(string tmp) //状态转换函数
{
bool save=true; //是否将当前字符存入匹配单词字符
TokenType currentToken; //当前的字符
TokenState state; //当前的状态
string tokenString=""; //用于存放待匹配的单词
if(flag==false) //如果当前还处在注释内部
state=START;
else
state=INDELETE; //那么继续返回注释状态
for(int i=0;i<=tmp.length();i++) //按照行为单位读入
{
save=true;
switch(state) //判断当前状态
{
case START: //开始状态下,之后状态参考DFA图片!
if(isnum(tmp[i]))
state=INNUM;
else if(isalpha(tmp[i]))
state=INID;
else if(tmp[i]=='<')
state=INLT;
else if(tmp[i]=='>')
state=INGT;
else if(tmp[i]=='=')
state=INEQ;
else if(tmp[i]=='!')
state=INNOT;
else if(tmp[i]==' '||tmp[i]=='\t'||tmp[i]=='\n')
save=false;
else if(tmp[i]=='/')
state=INCOMMENT;
else
{
state=DONE;
switch(tmp[i])
{
case '\0': //读取注释到了最后,仍然没有结束,此时返回特殊的状态
save=false;
currentToken=ENDFILE;
break;
case '+':
currentToken=ADD;
break;
case '-':
currentToken=SUB;
break;
case '*':
currentToken=MUL;
break;
case '(':
currentToken=LXK;
break;
case ')':
currentToken=RXK;
break;
case '[':
currentToken=LZK;
break;
case ']':
currentToken=RZK;
break;
case '{':
currentToken=LDK;
break;
case '}':
currentToken=RDK;
break;
case ';':
currentToken=FH;
break;
case ',':
currentToken=DH;
break;
default:
currentToken=ERROR;
break;
}
}
break;
case INCOMMENT: //将进入注释状态的时候
if(tmp[i]!='*')
{
save=true; //如果是连续的/*,那么进入注释,否则只有一个/,返回除法
state=DONE;
i--;
currentToken=DIV;
}
else
{
state=INDELETE;
flag=true;
}
break;
case INDELETE:
save=true;
if(tmp[i]=='*')
state=READDELETE; //正式进入注释,如果有一个*,进入到将要退出注释的状态
if(tmp[i]=='\0')
{
state=DONE;
currentToken=ENDFILE; //如果当前行所有都已经结束,那么直接把所有的字符给到注释
}
break;
case READDELETE: //将要退出注释的状态,此时如果再来一个/,退出注释,否则返回正式注释状态
if(tmp[i]=='/')
{
state=DONE;
flag=false;
currentToken=ENDFILE;
}
else
{
i--;
state=INDELETE;
save=false;
}
break;
case INLT: //<和<=第一个字符都是<,=和==第一个字符都是=,>和>=第一个字符都是>,!和!=第一个字符都是!,下面4种状态,如果没有匹配到第二个“=”,需要退回一个字符
state=DONE;
if(tmp[i]=='=')
currentToken=LE;
else
{
save=false;
i--;
currentToken=L;
}
break;
case INGT:
state=DONE;
if(tmp[i]=='=')
currentToken=GE;
else
{
save=false;
i--;
currentToken=G;
}
break;
case INEQ:
state=DONE;
if(tmp[i]=='=')
currentToken=ISE;
else
{
save=false;
i--;
currentToken=FZ;
}
break;
case INNOT:
state=DONE;
if(tmp[i]=='=')
currentToken=NTE;
else
{
save=false;
i--;
currentToken=NT;
}
break;
case INNUM: //数字和字母状态,只要接下来不能连续匹配到数字和字母,那么就会停止匹配
if(!isdigit(tmp[i]))
{
i--;
save=false;
state=DONE;
currentToken=NUM;
}
break;
case INID:
if(!isalpha(tmp[i]))
{
i--;
save=false;
state=DONE;
currentToken=ID;
}
break;
case DONE:break;
default:
state=DONE;
currentToken=ERROR;
break;
}
if(save && tmp[i]!='\0')
tokenString+=tmp[i]; //保存好当前的字符,加入匹配字符串
if(currentToken==ID) //如果是ID类型,那么找一找有没有可能会是预留类
currentToken=Find(tokenString);
if(state==DONE) //如果匹配完毕
{
PrintToken(currentToken,tokenString); //输出!
tokenString=""; //匹配字符串归零,开始下一轮
if(flag==false) //flag表示是否在注释内,如果当前注释还没结束,需要退回到注释内部状态
state=START;
else
state=INDELETE;
}
}
LINE++;
}
void read() //从lab2.txt读取数据
{
fstream ff("lab2.txt",ios::in);
assert(ff.is_open());
string tmp;
cout<<"C MINUS COMPILATION:lab2.txt"<<endl;
while(!ff.eof())
{
getline(ff,tmp);
{
cout<<endl;
cout<<"LINE"<<LINE<<":"<<tmp;
if(!ff.eof())
cout<<endl;
}
if(ff.eof())
cout<<"EOF"<<endl;
getToken(tmp);
}
}
int main(int argc,char* argv[])
{
read();
return 0;
}