一、实验目的
- 学会针对DFA转换图实现相应的高级语言源程序。
- 深刻领会状态转换图的含义,逐步理解有限自动机。
- 掌握手工生成词法分析器的方法,了解词法分析器的内部工作原理。
- 加强对C语言的掌握
二、实验内容
C计算机语言的编译程序的词法分析部分实现。
从左到右扫描每行该语言源程序的符号,拼成单词,换成统一的内部表示(token)送给语法分析程序。
为了简化程序的编写,有具体的要求如下:
- 空白符仅仅是空格、回车符、制表符。
- 代码是自由格式。
- 注释应放在花括号之内,并且不允许嵌套
C语言的单词
保留字 | 特殊符号 | 其他 |
if | + | 标识符 (首符号为字母或下划线,其他由一个或更多的字母或数字或下划线构成) |
else | - | |
while | * | |
do | / | |
main | = | 数 数字的属性包括三种:类型属性(整型、浮点型等)、进制属性(十进制、八进制、十六进制)和后缀属性(如短类型、宽类型等辅助类型信息) 如:123L |
int | < | |
float | { | |
double | } | |
return | ; | |
const | ( | |
void | ) | 字符常量 源程序中任意字符,除了’、backslash\、newline ‘a’ 字符串常量 源程序中任意字符,除了’、backslash\、newline “a” |
continue | ‘ | |
break | ‘ | |
char | “ | |
unsigned | “ | |
enum | == | |
long | != | |
switch | && | |
case | || | |
unsigned | > | |
auto | >= | |
static | <= |
三、实验要求
要求实现编译器的以下功能:
- 按规则拼单词,并转换成二元式形式
- 删除注释行
- 删除空白符 (空格、回车符、制表符)
- 显示源程序,在每行的前面加上行号,并且打印出每行包含的记号的二元形式
- 发现并定位错误。
词法分析进行具体的要求:
- 详细给出标识符、数字、字符和字符串的状态转换图
- 画出空白间隔符的状态转换图、换行符的状态装换图、注释(//、/* */)的状态转换图。
- 词法分析的具体功能实现是一个函数GetToken(),每次调用都对剩余的字符串分析得到一个单词或记号识别其种类,收集该记号的符号串属性,当识别一个单词完毕,采用返回值的形式返回符号的种类,同时采用程序变量的形式提供当前识别出记号的属性值。
- 标识符和保留字的词法构成相同,为了更好的实现,把语言的保留字建立一个表格存储,这样可以把保留字的识别放在标示符之后,用识别出的标示符对比该表格,如果存在该表格中则是保留字,否则是一般标识符。
四、算法分析
词法分析中识别下一个单词的过程,简单来看就是逐个读取字符,然后将他们拼在一起的过程。词法分析程序的作用就是在这个拼单词的过程中如何获得想爱一个有意义的单词符号,即识别出单词中别以及单词自身的值。
- 本实验使用C++语言,在主函数main()中利用fopen()函数打开文件test.txt ,并用fgetc()函数读入预先写好的测试样例,将其存入prog字符串。
- 定义关键字数组keyword,用以存储常见的保留字。(只存储了部分保留字,仍可扩展)。
- 定义全局变量line,记录当前的行数,以便于词法分析错误的时候快速定位错误位置。
- 然后按序读取字符,依据程序设计语言的词法规则描述,识别出有意义的单词符号,即调用GetToken()函数:如果当前字符为空格、回车符、制表符时,不进行操作、读取下一个字符,返回-1;如果当前字符为‘/’时,进一步判断是‘//’,还是‘/**/’;如果当前字符为字母时,默认为标识符后,与Keyword数组进行比较,若匹配则为关键字;如果当前字符是数字,默认为整数型,若遇到‘.’,则将其修改为浮点型;其他字符使用switch()实现判断匹配,匹配不成功则返回-2,以提示词法分析错误。直至字符串为空。
代码:
#include<iostream>
#include<string.h>
using namespace std;
//prog存放从文件读取的程序,token存放单词自身的字符串
char token[8], prog[1000], ch;
//columns为列数 ,sym表示单词的种别码
int p = 0, sym = 0, n = 0, line = 1;
//要读取的文件名
char filename[30];
/*FLE系统定义的结构体,*fpincontent是指向文件结构体的指针变量,
通过fp可找到存放某个文件信息的结构变量,根据这个结构变量的信息找到该文件,实施对文件的操作 */
FILE *fpincontent;
//关键字
char *keyword[22] = {"if", "else", "while", "do", "main", "int", "float", "double", "return", "const", "void", "continue", "break", "char", "unsigned", "enum", "long", "switch", "case", "unsigned", "auto", "static"};
void GetToken()
{
// 清空token数组
for(n = 0; n < 8; n++)
token[n] = '\0';
n = 0;
//读取字符
ch = prog[p++];
// 删除输入的空白字符,顺便判断是否换行,增加列数
while(ch == ' ' || ch == '\n' || ch == '\t')
{
//遇到‘\n’(回车换行) ,增加行数
if (ch == '\n')
{
line++;
}
//遇到‘\t’(横向跳到下一制表符位置)、‘ ’、‘\n’对其忽略,读取下一个字符
ch = prog[p++];
}
//删除注释行
if (ch == '/')
{
ch=prog[p++];
if(ch=='/') // “//”
{
do
{
ch = prog[p++];
} while(ch != '\n');
}
sym = -1;
if(ch=='*') // “/**/”
{
do
{
ch = prog[p++];
} while(ch != '*');
ch = prog[p++];
if(ch!='/')
{
sym=-2;
}
}
}
//识别标识符 、关键字
else if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
{
sym = 22; //标识符
do
{
token[n++] = ch;
ch = prog[p++];
} while((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'));
for(n = 0; n < 22; n++) //保留字
{
if(strcmp(token, keyword[n]) == 0)
sym = 23;
}
p--;
}
//识别数字
else if (ch >= '0' && ch <= '9')
{
sym = 24; //整数
do
{
token[n++] = ch;
ch = prog[p++];
} while(ch >= '0' && ch <= '9');
if(ch=='.') //小数
{
do
{
token[n++] = ch;
ch = prog[p++];
} while(ch >= '0' && ch <= '9');
sym = 25;
}
p--;
}
else
{
switch(ch)
{
case '+': sym = 0; token[0] = ch; break; //加
case '-': sym = 1; token[0] = ch; break; //减
case '*': sym = 2; token[0] = ch; break; //乘
case '/': sym = 3; token[0] = ch; break; //除
case ';': sym = 4; token[0] = ch; break; //语句结束
case '(': sym = 5; token[0] = ch; break;
case ')': sym = 6; token[0] = ch; break;
case '\'': sym = 7; token[0] = ch; break;
case '\"': sym = 8; token[0] = ch; break;
case '=':
{
sym = 9; //等于号
token[0] = ch;
ch = prog[p++];
if(ch == '=')
{
sym = 10; //判断是否相等
token[1] = ch;
}else {
p--;
}
break;
}
case '<':
{
sym = 11; //小于号
token[0] = ch;
ch = prog[p++];
if(ch == '=')
{
sym = 12;//小于等于
token[1] = ch;
}else {
p--;
}
break;
}
case '>':
{
sym = 13; //大于号
token[0] = ch;
ch = prog[p++];
if(ch == '=') //大于等于
{
sym = 14;
token[1] = ch;
}else {
p--;
}
break;
}
case '!':
{
token[0] = ch;
ch = prog[p++];
if(ch == '=')
{
sym = 15; //不等于
token[1] = ch;
}else {
p--;
sym = -2;
}
break;
}
case '&':
{
token[0] = ch;
ch = prog[p++];
if(ch == '&')
{
sym = 16; //且
token[1] = ch;
}else {
p--;
sym = -2;
}
break;
}
case '|':
{
token[0] = ch;
ch = prog[p++];
if(ch == '|')
{
sym = 17; //或
token[1] = ch;
}else {
p--;
sym = -2;
}
break;
}
case '#': sym = 18; token[0] = ch; break;
case '[': sym = 19; token[0] = ch; break;
case ']': sym = 20; token[0] = ch; break;
case ',': sym = 21; token[0] = ch; break;
case '{': sym = 26; token[0] = ch; break;
case '}': sym = 27; token[0] = ch; break;
default:
{
sym = -2;
break;
}
}
}
}
int main()
{
p = 0;
cout<< "read something from :" << endl<<" ";
for(;;)
{
cin>>filename;
//用 fopen函数打开文件,“r”为只读模式
fpincontent = fopen(filename,"r");
if(fpincontent!=NULL)
break;
else
cout<<"文件路径错误,请输入源文件名:"<<endl<<" ";
}
// 将文件读取到prog数组里
cout<<"源程序:"<<endl;
do
{
//fgetc()读取一个字符
ch = fgetc(fpincontent);
prog[p++] = ch;
cout<<ch;
}while(ch != EOF);
cout<<endl<<endl;
p = 0;
// 按规则拼单词,并转换成二元式形式
do
{
/*每次调用都对剩余的字符串分析得到一个单词或记号识别其种类,收集该记号的符号串属性,
当识别一个单词完毕,采用返回值的形式返回符号的种类,同时采用程序变量的形式提供当前识别出记号的属性值。*/
GetToken();
switch(sym)
{
case -1: break;
case -2: //返回值为-1、-2时,跳出switch
{
cout<<"词法分析出错!,错误位于第"<<line<<"行"<<endl;
break;
}
default: cout<<"第"<<line<<"行,<"<<sym<<" , "<<token<<">"<<endl;
}
}while(prog[p] != EOF);
cout<<endl<<endl;
p=0;
cout<<"修改后:"<<endl;
do
{
GetToken();
cout<<token;
} while(prog[p]!=EOF);
return 0;
}
运行结果: