所设计的输出单词符号二元式的组成为:
例如(1,while)、(7,123)、(6,hello)。
宏定义助记符id、num、relop。
单词符号 | 种别编码 | 助记符 | 内码值 |
---|---|---|---|
while | 1 | while | – |
if | 2 | if | – |
else | 3 | else | – |
switch | 4 | switch | – |
case | 5 | case | – |
标识符 | 6 | id | id在符号表中的位置 |
常数 | 7 | num | num在常数表中的位置 |
+ | 8 | + | – |
- | 9 | - | – |
* | 10 | * | – |
<= | 11 | relop | LE |
< | 11 | relop | LT |
== | 11 | relop | EQ |
= | 12 | = | – |
; | 13 | ; | – |
windows 10
visual studio 2019
if atest=1
hello world!;
else
d=d*atest-1;
while btest<=3
atest+c=1213;
switch syx
case 1 atest
代码主要基于《编译原理教程(第四版)》1 第二章 词法分析(p14)实现,尽量去贴合书上所给例题。
character 字符变量,存放最新读入的源程序字符
token 字符数组,存放构成单词符号的字符串
主要对输入串进行剔除多余的空白符操作。调用getbe()函数,若character中的字符为空白,则调用getchar_syx()函数(书上为getchar()),直至character为非空白符为止。
/*****************************************************************************
*函数名称:getbe()
*函数类型:void
*参数:void
*功能:滤白。若character中的字符为空白,则调用getchar_syx(),直至character为非空白字符为止
*****************************************************************************/
void getbe() {
while (character == ' ')
getchar_syx();
}
其中,getchar_syx()用来逐个读取文件中的字符。
/*****************************************************************************
*函数名称:getchar_syx()
*函数类型:void
*参数:void
*功能:逐个读取文件中的字符
*****************************************************************************/
void getchar_syx() {
if (fin)
character = fgetc(fin);
}
调用concatenation()函数,将token中的字符串与character中的字符连接并作为token中新的字符串。
/*****************************************************************************
*函数名称:concatenation()
*函数类型:void
*参数:void
*功能:将token中的字符串与character中的字符连接并作为token中新的字符串
*****************************************************************************/
void concatenation() {
token[token_point] = character;
token_point++;
token[token_point] = '\0';//字符串结束标志
}
调用letter()函数和digit()函数,判断character中的字符是否为字母和数字的布尔函数,是则返回true(即1),否则返回false(即0)。
/*****************************************************************************
*函数名称:letter()
*函数类型:_Bool
*参数:void
*功能:判断character中的字符是否为字母,是则返回true,否则返回false
*****************************************************************************/
_Bool letter() {
if ((character >= 'a') && (character <= 'z') || (character >= 'A') && (character <= 'Z'))
return 1;
else
return 0;
}
/*****************************************************************************
*函数名称:digit()
*函数类型:_Bool
*参数:void
*功能:判断character中的字符是否为数字,是则返回true,否则返回false
*****************************************************************************/
_Bool digit() {
if ((character >= '0') && (character <= '9'))
return 1;
else
return 0;
}
调用reserve()函数,按token数组中的字符串查保留字表,若是保留字的话,返回它的编码值,否则返回0值。
这里是定义了一个保留字表,存储保留字名称,用数组下标标识其编码值。
/*****************************************************************************
*函数名称:reserve()
*函数类型:int
*参数:void
*功能:判别token数组中的字符串是否为保留字,若为保留字则返回它的编码,否则返回0值
*****************************************************************************/
int reserve() {
for (int i = 1; i < reserved_Len; i++) {
if (strcmp(token, reserved_Word[i]) == 0) {
return i;
}
}
return 0;
}
调用retract()函数,扫描指针回退一个字符,同时将character置为空白。
/*****************************************************************************
*函数名称:retract()
*函数类型:void
*参数:void
*功能:扫描指针回退一个字符,同时将character置为空白
*****************************************************************************/
void retract() {
fseek(fin, -1, SEEK_CUR);
character = ' ';
}
调用buildlist()函数,将标识符登记到符号表中或将常数登记到常数表中。
(1)如果是常数,需要将字符串转换成数字(利用atoi函数)。
(2)无论是标识符还是常数,在登记入表时都需要查表看看表中是否已有该项了,如果表中没有项,才添加进表。
/*****************************************************************************
*函数名称:buildlist()
*函数类型:void
*参数:void
*功能:将标识符登记到符号表中或将常数登记到常数表中
*****************************************************************************/
void buildlist() {
char temp = token[0] - '0';//用于判断当前token中的字符串是标识符还是常数
/************************将常数登记到常数表中*****************************/
int temp_num = 0;//存放转换后的常数值
if (temp >= 0 && temp <= 9) {//如果是数字
temp_num = atoi(token);//利用atoi函数将token中的字符串转换成常数
int i;
for (i = 0; i < number_point; i++) {
if (temp_num == number_table[i]) {//如果常数表中已存在该常数
present_point = i;
break;
}
}
if (i == number_point) {//如果常数表中没有该常数,则登记该常数
number_table[number_point] = temp_num;
present_point = number_point;
number_point++;
}
}
/************************将标识符登记到常数表中*****************************/
else {
int i = 0;
for (i; i < id_point; i++) {
if (strcmp(token, identifier_table[i]) == 0) {//如果标识符表中已存在该字符串
present_point = i;
break;
}
}
if (i == id_point) {//如果标识符表中没有该字符串,则登记该标识符
strcpy_s(identifier_table[i],sizeof(token),token);
present_point = id_point;
id_point++;
}
}
}
唔,尽量贴合伪代码去写的。只是实验一,虽然后来课设也用到了,比较菜就看看吧。
/*****************************************************************************
*函数名称:test()
*函数类型:void
*参数:void
*功能:词法分析
*****************************************************************************/
void test() {
//strcpy_s(token, sizeof(token), " "); //对token数组初始化
token_point = 0; //token指针初始化
getchar_syx(); //获取字符
getbe(); //滤除空格
switch (character) {
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
/*字母开头,为标识符/保留字*/
while (letter() || digit()) {
/*标识符包括字母和数字*/
concatenation(); //将当前读入的字符送入token数组
getchar_syx();
}
retract(); //扫描指针回退一个字符
int c = reserve();
if (c == 0) {
buildlist(); //将标识符登录到符号表中
/*return(id, 指向id的符号表入口指针);*/
fprintf(fout, "(%d,%s)\t\t指向id的符号表入口指针:\t%d\n", id, identifier_table[present_point], present_point);
}
else {
/*return(保留字码,null);*/
fprintf(fout, "(%d,%s)\n", c, reserved_Word[c]);
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/*数字开头,为常数*/
while (digit()) {
/*常数只包括数字*/
concatenation();
getchar_syx();
}
retract();
buildlist(); //将常数登录到常数表中
/*return(num, num的常数表入口指针);*/
fprintf(fout, "(%d,%d)\t\t指向num的符号表入口指针:\t%d\n", num, number_table[present_point], present_point);
break;
case '+':
/*return('+', null);*/
fprintf(fout, "(%c,_)\n", '+');
break;
case '-':
/*return('-', null);*/
fprintf(fout, "(%c,_)\n", '-');
break;
case '*':
/*return('*', null);*/
fprintf(fout, "(%c,_)\n", '*');
break;
case '<':
getchar_syx();
if (character == '=')
fprintf(fout, "(%d,%s)\n", relop, "LE");
/*reutrn(relop, LE);*/
else {
retract();
/*return(relop, LT);*/
fprintf(fout, "(%d,%s)\n", relop, "LT");
}
break;
case '=':
getchar_syx();
if (character == '=')
fprintf(fout, "(%d,%s)\n", relop, "EQ");
/*return(relop, EQ);*/
else {
retract();
fprintf(fout, "(%c,_)\n", '=');
/*return('=', null);*/
}
break;
case ';':
fprintf(fout, "(%c,_)\n", ';');
/*return(';', null);*/
break;
default:
if (character == '\n' || character == '\t' || character == EOF)
break;
error(); //调用出错函数
}
}
调用error()函数,出现非法字符,显示出错信息。
/*****************************************************************************
*函数名称:error()
*函数类型:void
*参数:void
*功能:出现非法字符,显示出错信息
*****************************************************************************/
void error() {
printf("非法字符:%c\n", character);
}
关于errno返回值,看大佬的 Linux errno详解.
不想了解的话,只要知道err_in和err_out的返回值为0正确就行。
int main() {
errno_t err_in, err_out;
err_in = fopen_s(&fin, "E:\\test.txt", "r");
err_out = fopen_s(&fout, "E:\\testout.txt", "w+");
while (character != EOF)
test();
if (fin)
fclose(fin);
if (fout)
fclose(fout);
return 0;
}
保存在testout.txt中。
(2,if)
(6,atest) 指向id的符号表入口指针: 0
(=,_)
(7,1) 指向num的符号表入口指针: 0
(6,hello) 指向id的符号表入口指针: 1
(6,world) 指向id的符号表入口指针: 2
(;,_)
(3,else)
(6,d) 指向id的符号表入口指针: 3
(=,_)
(6,d) 指向id的符号表入口指针: 3
(*,_)
(6,atest) 指向id的符号表入口指针: 0
(-,_)
(7,1) 指向num的符号表入口指针: 0
(;,_)
(1,while)
(6,btest) 指向id的符号表入口指针: 4
(11,LE)
(7,3) 指向num的符号表入口指针: 1
(6,atest) 指向id的符号表入口指针: 0
(+,_)
(6,c) 指向id的符号表入口指针: 5
(=,_)
(7,1213) 指向num的符号表入口指针: 2
(;,_)
(4,switch)
(6,syx) 指向id的符号表入口指针: 6
(5,case)
(7,1) 指向num的符号表入口指针: 0
(6,atest) 指向id的符号表入口指针: 0
(11,LT)
(6,btest) 指向id的符号表入口指针: 4
(;,_)
(5,case)
(7,2) 指向num的符号表入口指针: 3
(6,atest) 指向id的符号表入口指针: 0
(11,EQ)
(6,btest) 指向id的符号表入口指针: 4
(;,_)
%{
#include <stdio.h>
#include <stdlib.h>
int count = 0;
/*
* digit 数字
* letter 字母
* reservedWord 保留字
* identifier 标识符
* constant 常数
* operator 运算符
* delim 空白符
* whitespace 空白符组合
* other 非法字符
*/
%}
digit [0-9]
letter [a-zA-Z]
reservedWord [w][h][i][l][e]|[i][f]|[e][l][s][e]|[s][w][i][t][c][h]|[c][a][s][e]
identifier {letter}({letter}|{digit})*
constant {digit}+
operator \+|-|\*|<=|<|==|=|;
delim [ \t\n\r]
whitespace {delim}+
other .
%%
{reservedWord} {count++;fprintf(yyout,"%d\t(1,‘%s’)\n",count,yytext);}
{identifier} {count++;fprintf(yyout,"%d\t(2,‘%s’)\n",count,yytext);}
{constant} {count++;fprintf(yyout,"%d\t(3,‘%s’)\n",count,yytext);}
{operator} {count++;fprintf(yyout,"%d\t(4,‘%s’)\n",count,yytext);}
{whitespace} { /* do nothing*/ }
{other} {fprintf(yyout,"非法字符:'%s'\n",yytext);}
%%
void main()
{
yyin=fopen("test.txt","r");
yyout=fopen("testout.txt","w+");
yylex(); /* start the analysis*/
fclose(yyout);
fclose(yyin);
}
int yywrap()
{
return 1;
}
testout.txt
1 (1,‘if’)
2 (2,‘atest’)
3 (4,‘=’)
4 (3,‘1’)
5 (2,‘hello’)
6 (2,‘world’)
非法字符:'!'
7 (4,‘;’)
8 (1,‘else’)
9 (2,‘d’)
10 (4,‘=’)
11 (2,‘d’)
12 (4,‘*’)
13 (2,‘atest’)
14 (4,‘-’)
15 (3,‘1’)
16 (4,‘;’)
17 (1,‘while’)
18 (2,‘btest’)
19 (4,‘<=’)
20 (3,‘3’)
21 (2,‘atest’)
22 (4,‘+’)
23 (2,‘c’)
24 (4,‘=’)
25 (3,‘121’)
26 (4,‘;’)
27 (1,‘switch’)
28 (2,‘syx’)
29 (1,‘case’)
30 (3,‘1’)
31 (2,‘atest’)
32 (4,‘<’)
33 (2,‘btest’)
34 (4,‘;’)
35 (1,‘case’)
36 (3,‘2’)
37 (2,‘atest’)
38 (4,‘==’)
39 (2,‘btest’)
40 (4,‘;’)
这个词法分析器在最后课设(实现一个小型编译程序)的时候也用到了
下载地址词法分析器.
胡元义.编译原理教程(第四版)[M].西安电子科技大学出版社:西安,2015:9-34. ↩︎