一.实验内容
C++计算机语言的编译程序的词法分析部分实现。
从左到右扫描每行该语言源程序的符号,拼成单词,换成统一的内部表示(token)送给语法分析程序。
为了简化程序的编写,有具体的要求如下:
- 空白符仅仅是空格、回车符、制表符。
- 代码是自由格式。
- 注释应放在花括号之内,并且不允许嵌套
保留字 |
特殊符号 |
if |
+ |
else |
- |
while |
* |
do |
/ |
main |
= |
int |
< |
float |
{ |
double |
} |
return |
; |
const |
( |
void |
) |
continue |
‘ |
break |
‘ |
char |
“ |
unsigned |
“ |
enum |
== |
long |
!= |
switch |
&& |
case |
|| |
unsigned |
> |
auto |
>= |
static |
<= |
其他 |
标识符 (首符号为字母或下划线,其他由一个或更多的字母或数字或下划线构成) |
数 数字的属性包括三种:类型属性(整型、浮点型等)、进制属性(十进制、十六进制) |
二.要求实现编译器的以下功能:
- 按规则拼单词,并转换成二元式形式
- 删除注释行
- 删除空白符 (空格、回车符、制表符)
- 显示源程序,在每行的前面加上行号,并且打印出每行包含的记号的二元形式
- 发现并定位错误。
词法分析进行具体的要求:
- 详细给出标识符、数字、字符和字符串的状态转换图
- 画出空白间隔符的状态转换图、换行符的状态装换图、注释(//、/* */)的状态转换图。
(3) 词法分析的具体功能实现是一个函数GetToken(),每次调用都对剩余的字符串分析得到一个单词或记号识别其种类,收集该记号的符号串属性,当识别一个单词完毕,采用返回值的形式返回符号的种类,同时采用程序变量的形式提供当前识别出记号的属性值。
(4) 标识符和保留字的词法构成相同,为了更好的实现,把语言的保留字建立一个表格存储,这样可以把保留字的识别放在标示符之后,用识别出的标示符对比该表格,如果存在该表格中则是保留字,否则是一般标识符。
三.状态转换图
程序代码:
#include<iostream>
#include <string.h>
using namespace std;
char prog[1000], token[8], ch; //prog为存放文件字符的数组、token为统一内部表示数组,ch为文件字符提取器
int p = 0, sym = 0, n; //sym为状态标识
char filename[30]; //存储文件路径
FILE* fpin; //声明函数指针
const char* keyword[23] = { "if","else","while","do","main","int","float","double","return","const","void","continue","break","char","unsigned","enum","long","switch","case","unsigned","auto","static" }; //存储关键字
void GetToken(); //词法分析函数
int main()
{
p = 0;
cout << "请输入源文件名:";
for (;;)
{
cin >> filename; //输入文件名
errno_t err = fopen_s(&fpin, filename, "r"); //errno_t是一个数据类型,实际上是整形,如果打开文件成功,fopen_s函数返回0
if (err==0) //用*fpin指针以只读方式打开文件
break;
else cout << "文件路径错误!请输入源文件名:";
}
do
{
ch = fgetc(fpin); //从fpin指针所指的文件中读取一个字符赋给字符变量ch
prog[p++] = ch;
} while (ch != EOF); //ch与文件结束字符是否相等
p = 0;
do
{
GetToken();
switch (sym)
{
case -1: //处理注释
case -2: break; //词法分析出错
default:cout << "(" << sym << "," << token << ")" << endl; break; //输出词法分析结果
}
} while (ch != EOF);
}
//扫描器
void GetToken()
{
for (n = 0; n < 8; n++)
{
token[n] = '\0'; //每次要置空
}
n = 0;
ch = prog[p++];
/**预处理空格、换行、制表符**/
while (ch == ' ' || ch == '\n' || ch == '\t')
{
ch = prog[p++]; //接着往下读
}
/**处理标识符**/
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_')
{
sym = 1;
do {
token[n++] = ch;
ch = prog[p++];
} while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_');
sym = 2;
/**处理关键字、保留字**/
for (n = 0; n < 22; n++)
{
if (strcmp(token, keyword[n]) == 0) //字符串比较
{
sym = n + 3; //对应状态转换标识
}
}
p--; //回退,之前p多加1
}
/**处理注释及除法 **/
else if (ch == '/')
{
ch = prog[p++]; //还不能确定是什么,所以要检测下一个
/**下一个还是“ / ”或“*”确定是注释**/
if (ch == '/')
{
do {
ch = prog[p++];
} while (ch == '\n');
}
if (ch == '*')
{
do {
ch = prog[p++];
} while (ch == '/');
}
sym = -1;
if (ch != '*' && ch != '/') //是除法运算符
{
ch = prog[p - 2]; //回退到字符“/”
p--;
sym = 31; token[0] = ch;
}
return;
}
/**处理数字 **/
else if (ch >= '0' && ch <= '9') //整数
{
do
{
token[n++] = ch;
ch = prog[p++];
} while (ch >= '0' && ch <= '9');
if (ch != '.' && !isalnum(ch)) //isalnum用来判断该字符变量存的是不是数字和字母
{
sym = 25;
}
else if (ch == '.') //浮点数
{
do
{
token[n++] = ch;
ch = prog[p++];
} while (ch >= '0' && ch <= '9');
sym = 26;
}
else if ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) //16进制
{
do
{
token[n++] = ch;
ch = prog[p++];
} while (ch != 'H' && ch != 'h'); //遇到“H”和“h”结束
sym = 27;
}
p--;
return;
}
else
{
switch (ch)
{
case '+':sym = 28; token[0] = ch; break;
case '-':sym = 29; token[0] = ch; break;
case '*':sym = 30; token[0] = ch; break;
case '=':
token[0] = ch;
ch = prog[p++]; //探测下一个
if (ch == '=') //是“==”
{
sym = 37; token[1] = ch; break;
}
else { sym = 32; p--; break; } //如果就是“=”,回退一个
case '<':
token[0] = ch;
ch = prog[p++];
if (ch == '=')
{
sym = 36; token[1] = ch; break;
}
else { sym = 33; p--; break; }
case '>':
token[0] = ch;
ch = prog[p++];
if (ch == '=')
{
sym = 35; token[1] = ch; break;
}
else { sym = 34; p--; break; }
case '!':
token[0] = ch;
ch = prog[p++];
if (ch == '=')
{
sym = 38; token[1] = ch; break;
}
else
{
sym = -2;
p--;
}
case '{':sym = 39; token[0] = ch; break;
case '}':sym = 40; token[0] = ch; break;
case ';':sym = 41; token[0] = ch; break;
case '(':sym = 42; token[0] = ch; break;
case ')':sym = 43; token[0] = ch; break;
case '"':sym = 44; token[0] = ch; break;
case '&':
token[0] = ch;
ch = prog[p++];
if (ch == '&')
{
sym = 45; token[1] = ch; break;
}
else
{
sym = -2; //单个“&”字符,不识别
p--;
}
case '|':
token[0] = ch;
ch = prog[p++];
if (ch == '|')
{
sym = 46; token[1] = ch; break;
}
else
{
sym = -2;
p--;
}
default: sym = -2; cout << "词法分析出错,请检查是否输入非法字符!" << endl; break;
}
}
}
参考文献:
《编译原理及实现》 姜淑娟、张辰、刘兵