编译原理实验1–词法分析
BY --ELFer
实验目的
设计编制调试一个具体的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。
编制一个读单词过程,从输入的源程序中,识别出具有独立意义的单词,即基本保留字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的内部编码及单词符号自身值。
实验内容
-
回顾词法分析的过程,明确要区分出保留字、标识符、常数、运算符、分隔符五大类单词的哪些词,确定每类的种别码。
-
针对每一类单词,设计程序,以达到提取识别的目的。 代码实现每一部分的功能,并组装起来完整的词法分析程序。
-
选择被测试的代码,测试程序运行结果。
实验步骤
- 根据要求明确要识别的内容:
保留字:if、int、for、while、do、return、break、continue; 单词种别码为 1。
其他的都识别为标识符;单词种别码为 2。
常数为无符号整形数;单词种别码为 3。
运算符包括:+、-、*、/、=、>、=、<=、!=;单词种别码为 4。
分隔符包括:,、;、{、}、(、); 单词种别码为 5 - 思路的大致的规划,选择使用文件来实现源代码的读入,输出为二元组的形式,所以在分析程序中设置syn用来记录标识符,token[]数组,保存每次提取出的单词符号自身值,采用引用的方式带回各个函数的识别结果。
- 五大类单词的各自识别程序设计与实现
1.程序的读取
将要分析的程序存入txt文件中,使用fopen_s()函数以读的方式打开文件,然后使用文件指针fp和fgetc()函数,一个个读取字符。
FILE* fp = NULL;
char fname[] = "实验一.txt";
cout << "词法分析程序" << endl << "编制人: 学号:116132019 班级:计算机科学与技术班" << endl;
cout << "-------------------------------------------------------------" << endl;
if (fopen_s(&fp, fname, "r") == NULL)//fopen_s()打开返回0
{
cout<<"词法分析结果"<<endl;
Scanner(fp);
}
2.保留字、单词的识别
首先判断一个字符是不是字母,如果是字母的话,继续尝试拼出单词。单词拼出之后,再进行区分该单词是保留字还是其他标识符。
设置三个函数:is_letter()用来识别是否为字母,make_word()用来拼出单词,judge_word()判断是否为关键字。
拼单词函数,设置一个while循环,只要文件没有到结尾且取出的字符是单词,就将字母存入token[]数组中,直到取到非字母元素结束。
判断是否为保留字,将保留字存入二维数组中,采用while循环一个个对比。如果配对上则将syn值记为1,否则记为2。
bool is_letter(char ch) {
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
return true;
else return false;
}
void make_word(FILE* p, char* token) {
int j = 1;
char ch;
while ((ch=fgetc(p))!=EOF&&is_letter(ch)) {
token[j++] = ch;
//将字母往token[]数组中存,直到非字母元素停止
}
fseek(p, -1, 1);//取出非字母的元素后,指针指向非字母元素的下一个,所以要指针往前移一位
}
void judge_word(string str, int& syn) {
char key[8][10] = { "if","int","for","while","do","return","break","continue" };//定义一个二维数组存放关键字
for (int i = 0; i < 6; i++)
{
if (str == key[i])
{
syn = 1;//是关键字,syn为1
return;
}
}
syn = 2;//非关键字,syn为2
}
3.无符号整形数的识别
与单词的识别类似,先判断单个字符是否为数字,如果是字符,就尝试拼数字。
设置两个函数:is_num()判断是否为数字,make_num()拼出数字。
bool is_num(char ch)
{
if (ch >= '0' && ch <= '9')
return true;
else
return false;
}
void make_num(FILE* p, char* token) {
int j = 1;
char ch;
while ((ch=fgetc(p))!=EOF&&is_num(ch)) {
token[j++] = ch;
//将数字拼起来放到token数组中,方法同拼单词
}
fseek(p, -1, 1);
}
4.运算符的识别
因为每次只取出一个字符,所以分别识别一位的运算符和两位的运算符。
首先判断当前取出的字符是否为+、-、*、/、=、>、<、!,如果是,则进一步判断,当前字符为>、<、!时,下一位是否为=,即是否为>=、<=、!=运算符。
ool is_operator(FILE* p, char ch,char* token, int& syn) {
//单目运算符,直接使用if语句
token[0] = ch;
if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '=' || ch == '>' || ch == '<' || ch == '!')
{
syn = 4;//满足一位运算符条件
//判断两位运算符
if (ch == '>' || ch == '<' || ch == '!')
{
char ch2 = fgetc(p);//取ch的下一个字符,判断能否构成两位运算符
if (ch2 == '=' )token[1] = ch2;
else fseek(p, -1, 1);
//不能构成两位运算符的话,后退一个字符,重新指向单位运算符的下一位
}
return true;
}
//非运算符,返回-1;
return false;
}
5.分隔符的识别
与运算符的判断类似,但由于分隔符都只有一位,所以直接判断当前字符是否为分隔符即可。
bool is_separator(char ch, char* token, int& syn) {
int j = 0;
//if语句判断分隔符
if (ch == ',' || ch == ';' || ch == '{' || ch == '}' || ch == '(' || ch == ')')
{
token[j] = ch;
syn = 5;
return true;
}
return false;
}
6.主控程序
void Scanner(FILE* p) {
int syn;//标识符,记录元素的种别码
char ch;
while ((ch = fgetc(p)) != EOF) {
char token[10] = "";//暂时放拼好的单词,数字,获得的字符,每轮循环初始化为空
if (is_letter(ch)) {
token[0] = ch;//是字母的话,先将已取出的字母存入到token数组中
make_word(p, token);//拼单词
string str = token;//拼好的单词放入字符串中
judge_word(str, syn);//判断是不是关键字
cout << "(" << syn << ",\"" << token << "\")" << endl;
continue;
}
else if (is_num(ch)) {
token[0] = ch;
make_num(p, token);//拼数字
syn = 3;
cout << "(" << syn << ",\"" << token << "\")" << endl;
continue;
}
else if (is_operator(p, ch, token, syn))
{
cout << "(" << syn << ",\"" << token << "\")" << endl;
continue;
}
else if (is_separator(ch, token, syn))
{
cout << "(" << syn << ",\"" << token << "\")" << endl;
continue;
}
else
{
continue;//其他字符,错误字符或‘ ’、‘\n’,直接跳过,不做处理
}
}
}