- 源代码及附件:编译原理实验一源程序及附件资源-CSDN文库
- 实验题目
- 实验要求
- 实验设计
前两部分指出了实验的宏观把控,为了具体实施实验,我们需要预先为实验做出如下设计:
本次实验我选取了C语言的一个子集进行设计词法分析器,其中单词种类如下:(也可参考附件中的单词种类表)
根据题目要求,我们用正则文法来定义我们的子集:
- S→关键字|运算符|分界符|整型常量|浮点型常量|标识符
- 关键字
→ void|main|int|float|for|while|switch|case|if|else|return|break
(3)运算符 → +|-|*|/|=|==|<|<=|>|>=
(4)分界符 → (|)|[|]|{|}|,|;|:
(5)整型常量 → digit(digit)*
(6)浮点型常量 → digit(digit).digit(digit)
(7)digit→ 0|1|2|3|4|5|6|7|8|9
(8)letter→ a|b|…|z|A|B|…|Z
(9)标识符 → letter(letter|digit)*
- 实验分析
基本算法思想是:读取文件,逐个字符分析,若是空白符则跳过,为字母时将连续的字母使用超前搜索组合成为变量或关键字;若是数字,则要判断是否为浮点数,即利用超前搜索,判断扫描到的字符是否为小数点;若是分隔符或者操作符,利用switch语句判断并输出,若是其他字符,输出为未定义的字符。
相关代码段:
void lexicalAnalysis(FILE* fp)
{
char ch;
while ((ch = fgetc(fp)) != EOF) /
{
token = ch;
if (ch == ' ' || ch == '\t' || ch == '\n') //忽略空格、Tab和回车
{
if (ch == '\n') //遇到换行符,记录行数的row加1
row++;
continue;
}
else if (isLetter(ch)) //以字母开头,关键字或标识符
{
token = ""; //token初始化
while (isLetter(ch) || isDigit(ch)) //非字母或数字时退出,将单词存储在token中
{
token.push_back(ch); //将读取的字符ch存入token中
ch = fgetc(fp); //获取下一个字符
}
//文件指针后退一个字节,即重新读取上述单词后的第一个字符
fseek(fp, -1L, SEEK_CUR);
if (isKey(token)) //关键字
code = TokenCode(getKeyID(token));
else //标识符
code = TK_IDENT; //单词为标识符
}
else if (isDigit(ch)) //无符号常数以数字开头
{
int isdouble = 0; //标记是否为浮点数
token = ""; //token初始化
while (isDigit(ch)) //当前获取到的字符为数字
{
token.push_back(ch); //读取数字,将其存入token中
ch = fgetc(fp); //从文件中获取下一个字符
//该单词中第一次出现小数点
if (ch == '.' && isdouble == 0)
{
//小数点下一位是数字
if (isDigit(fgetc(fp)))
{
isdouble = 1; //标记该常数中已经出现过小数点
fseek(fp, -1L, SEEK_CUR); //将超前读取的小数点后一位重新读取
token.push_back(ch); //将小数点入token中
ch = fgetc(fp); //读取小数点后的下一位数字
}
}
}
if (isdouble == 1)
code = TK_DOUBLE; //单词为浮点型
else
code = TK_INT; //单词为整型
//文件指针后退一个字节,即重新读取常数后的第一个字符
fseek(fp, -1L, SEEK_CUR);
}
else switch (ch)
{
/*运算符*/
case '+': code = TK_PLUS; //+加号
break;
case '-': code = TK_MINUS; //-减号
break;
case '*': code = TK_STAR; //*乘号
break;
case '/': code = TK_DIVIDE; //除号
break;
case '=':
{
ch = fgetc(fp); //超前读取'='后面的字符
if (ch == '=') //==等于号
{
token.push_back(ch); //将'='后面的'='存入token中
code = TK_EQ; //单词为"=="
}
else { //=赋值运算符
code = TK_ASSIGN; //单词为"="
fseek(fp, -1L, SEEK_CUR); //将超前读取的字符重新读取
}
}
break;
case '<':
{
ch = fgetc(fp); //超前读取'<'后面的字符
if (ch == '=') //<=小于等于号
{
token.push_back(ch); //将'<'后面的'='存入token中
code = TK_LEQ; //单词为"<="
}
else { //<小于号
code = TK_LT; //单词为"<"
fseek(fp, -1L, SEEK_CUR); //将超前读取的字符重新读取
}
}
break;
case '>':
{
ch = fgetc(fp); //超前读取'>'后面的字符
if (ch == '=') //>=大于等于号
{
token.push_back(ch); //将'>'后面的'='存入token中
code = TK_GEQ; //单词为">="
}
else { //>大于号
code = TK_GT; //单词为">"
fseek(fp, -1L, SEEK_CUR); //将超前读取的字符重新读取
}
}
break;
/*分界符*/
case '(': code = TK_OPENPA; //(左圆括号
break;
case ')': code = TK_CLOSEPA; //)右圆括号
break;
case '[': code = TK_OPENBR; //[左中括号
break;
case ']': code = TK_CLOSEBR; //]右中括号
break;
case '{': code = TK_BEGIN; //{左大括号
break;
case '}': code = TK_END; //}右大括号
break;
case ',': code = TK_COMMA; //,逗号
break;
case ';': code = TK_SEMOCOLOM; //;分号
break;
case':':code = TK_MAO;//:冒号
break;
//未识别符号
default: code = TK_UNDEF;
}
print(code); //打印词法分析结果
}
}
定义了如下数据结构作为状态终态:
/* 关键字 */
KW_VOID, //void关键字
KW_MAIN, //main关键字
KW_INT, //int关键字
KW_DOUBLE, //double关键字
KW_FOR, //for关键字
KW_WHILE, //while关键字
KW_SWITCH, //switch关键字
KW_CASE, //case关键字
KW_IF, //if关键字
KW_ELSE, //else关键字
KW_RETURN, //return关键字
KW_BREAK,//break关键字
/* 运算符 */
TK_PLUS, //+加号
TK_MINUS, //-减号
TK_STAR, //*乘号
TK_DIVIDE, ///除号
TK_ASSIGN, //=赋值运算符
TK_EQ, //==等于号
TK_LT, //<小于号
TK_LEQ, //<=小于等于号
TK_GT, //>大于号
TK_GEQ, //>=大于等于号
TK_MAO, //:冒号
/* 分隔符 */
TK_OPENPA, //(左圆括号
TK_CLOSEPA, //)右圆括号
TK_OPENBR, //[左中括号
TK_CLOSEBR, //]右中括号
TK_BEGIN, //{左大括号
TK_END, //}右大括号
TK_COMMA, //,逗号
TK_SEMOCOLOM, //;分号
/* 常量 */
TK_INT, //整型常量
TK_DOUBLE, //浮点型常量
/* 标识符 */
TK_IDENT
为了使得输出有所取分,使用不同颜色输出:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED); //未识别的符号为红色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE); //关键字为蓝色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); //运算符和分隔符为绿色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); //常量为黄色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY); //关键字为灰色
利用文件作为输入。
输入文档1.txt:
double add(double x, double y)
{
double a = 3.456;
return x + y;
}
$
int main()
{
int i;
switch(i)
{
case 1: break;
}
if(double(i,i)>i)
break;
int a[10];
}
输出结果为:
其中注意红色标记:
说明我们实现了错误识别的功能。
- 源代码
#include <iostream>
#include <string>
#include <Windows.h>
using namespace std;
/* 单词编码 */
enum TokenCode
{
/*未定义*/
TK_UNDEF = 0,
/* 关键字 */
KW_VOID, //void关键字
KW_MAIN, //main关键字
KW_INT, //int关键字
KW_DOUBLE, //double关键字
KW_FOR, //for关键字
KW_WHILE, //while关键字
KW_SWITCH, //switch关键字
KW_CASE, //case关键字
KW_IF, //if关键字
KW_ELSE, //else关键字
KW_RETURN, //return关键字
KW_BREAK,//break关键字
/* 运算符 */
TK_PLUS, //+加号
TK_MINUS, //-减号
TK_STAR, //*乘号
TK_DIVIDE, ///除号
TK_ASSIGN, //=赋值运算符
TK_EQ, //==等于号
TK_LT, //<小于号
TK_LEQ, //<=小于等于号
TK_GT, //>大于号
TK_GEQ, //>=大于等于号
TK_MAO, //:冒号
/* 分隔符 */
TK_OPENPA, //(左圆括号
TK_CLOSEPA, //)右圆括号
TK_OPENBR, //[左中括号
TK_CLOSEBR, //]右中括号
TK_BEGIN, //{左大括号
TK_END, //}右大括号
TK_COMMA, //,逗号
TK_SEMOCOLOM, //;分号
/* 常量 */
TK_INT, //整型常量
TK_DOUBLE, //浮点型常量
/* 标识符 */
TK_IDENT
};
TokenCode code = TK_UNDEF; //记录单词的种别码
const int MAX = 12; //关键字数量
int row = 1; //记录字符所在的行数
string token = ""; //用于存储单词
char keyWord[][10] = { "void","main","int","double","for","while","switch","case","if","else","return","break"}; //存储关键词
void print(TokenCode code)
{
switch (code)
{
/*未识别的符号*/
case TK_UNDEF:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED); //未识别的符号为红色
cout << '(' << code << ',' << token << ")" << "未识别的符号在第" << row << "行。" << endl;
return;
break;
/*关键字*/
case KW_VOID:
case KW_MAIN:
case KW_INT:
case KW_DOUBLE:
case KW_FOR:
case KW_WHILE:
case KW_SWITCH:
case KW_CASE:
case KW_IF:
case KW_ELSE:
case KW_RETURN:
case KW_BREAK:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE); //关键字为蓝色
break;
/* 运算符 */
case TK_PLUS:
case TK_MINUS:
case TK_STAR:
case TK_DIVIDE:
case TK_ASSIGN:
case TK_EQ:
case TK_LT:
case TK_LEQ:
case TK_GT:
case TK_GEQ:
/* 分隔符 */
case TK_OPENPA:
case TK_CLOSEPA:
case TK_OPENBR:
case TK_CLOSEBR:
case TK_BEGIN:
case TK_END:
case TK_COMMA:
case TK_SEMOCOLOM:
case TK_MAO:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); //运算符和分隔符为绿色
break;
/* 常量 */
case TK_INT:
case TK_DOUBLE:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); //常量为黄色
if (token.find('.') == token.npos)
cout << '(' << code << ',' << atoi(token.c_str()) << ")" << endl;
else
cout << '(' << code << ',' << atof(token.c_str()) << ")" << endl;
return;
break;
/* 标识符 */
case TK_IDENT:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY); //关键字为灰色
break;
default:
break;
}
cout << '(' << code << ',' << token << ")" << endl;
}
bool isKey(string token)
{
for (int i = 0; i < MAX; i++)
{
if (token.compare(keyWord[i]) == 0)
return true;
}
return false;
}
int getKeyID(string token)
{
for (int i = 0; i < MAX; i++)
{ //关键字的内码值为keyWord数组中对应的下标加1
if (token.compare(keyWord[i]) == 0)
return i + 1;
}
return -1;
}
bool isLetter(char letter)
{
if ((letter >= 'a' && letter <= 'z') || (letter >= 'A' && letter <= 'Z'))
return true;
return false;
}
bool isDigit(char digit)
{
if (digit >= '0' && digit <= '9')
return true;
return false;
}
void lexicalAnalysis(FILE* fp1)
{
char ch;
while ((ch = fgetc(fp1)) != EOF)
{
token = ch;
if (ch == ' ' || ch == '\t' || ch == '\n')
{
if (ch == '\n')
row++;
continue;
}
else if (isLetter(ch))
{
token = "";
while (isLetter(ch) || isDigit(ch))
{
token.push_back(ch);
ch = fgetc(fp1);
}
fseek(fp1, -1L, SEEK_CUR);
if (isKey(token))
code = TokenCode(getKeyID(token));
else
code = TK_IDENT;
}
else if (isDigit(ch))
{
int isdouble = 0;
token = "";
while (isDigit(ch))
{
token.push_back(ch);
ch = fgetc(fp1);
if (ch == '.' && isdouble == 0)
{
if (isDigit(fgetc(fp1)))
{
isdouble = 1;
fseek(fp1, -1L, SEEK_CUR);
token.push_back(ch);
ch = fgetc(fp1);
}
}
}
if (isdouble == 1)
code = TK_DOUBLE;
else
code = TK_INT;
fseek(fp1, -1L, SEEK_CUR);
}
else switch (ch)
{
case '+': code = TK_PLUS;
break;
case '-': code = TK_MINUS;
break;
case '*': code = TK_STAR;
break;
case '/': code = TK_DIVIDE;
break;
case '=':
{
ch = fgetc(fp1);
if (ch == '=')
{
token.push_back(ch);
code = TK_EQ;
}
else {
code = TK_ASSIGN;
fseek(fp1, -1L, SEEK_CUR);
}
}
break;
case '<':
{
ch = fgetc(fp1);
if (ch == '=')
{
token.push_back(ch);
code = TK_LEQ;
}
else {
code = TK_LT;
fseek(fp1, -1L, SEEK_CUR);
}
}
break;
case '>':
{
ch = fgetc(fp1);
if (ch == '=')
{
token.push_back(ch);
code = TK_GEQ;
}
else {
code = TK_GT;
fseek(fp1, -1L, SEEK_CUR);
}
}
break;
case '(': code = TK_OPENPA;
break;
case ')': code = TK_CLOSEPA;
break;
case '[': code = TK_OPENBR;
break;
case ']': code = TK_CLOSEBR;
break;
case '{': code = TK_BEGIN;
break;
case '}': code = TK_END;
break;
case ',': code = TK_COMMA;
break;
case ';': code = TK_SEMOCOLOM;
break;
case':':code = TK_MAO;
break;
default: code = TK_UNDEF;
}
print(code); //打印词法分析结果
}
}
int main()
{
string filename;
FILE* fp1;
cout << "请输入源文件名:" << endl;
while (true) {
cin >> filename;
if ((fopen_s(&fp1, filename.c_str(), "r")) == 0)
break;
else
cout << "路径输入错误!" << endl;
}
cout << "/=***************************词法分析结果***************************=/" << endl;
lexicalAnalysis(fp1); //词法分析
fclose(fp1);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); //字体恢复原来的颜色
return 0;
}
- 实验总结
这是编译原理的第一次实验,之前因为理解题意不明,也与老师探讨了多次,上网查阅各种资料等等,最终终于完成了实验。
在实验过程中发生过种种问题,一开始为图方便,想到过使用硬编码来实现实验,但最终还是使用了FA的方式。
通过本次实验,我对NFA、DFA的知识有了更深入的了解,编程能力也进一步增强,可以说这次实验是我对编译原理与实际应用的第一次初步实现,对我的影响深远!