资源描述
南华大学计算机科学与技术学院 实验报告
1. 实验目的及要求
1、 目的
通过设计、编制、调试一个具体的词法分析程序,加深对词法分析原理的理解,并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。
2、 要求
l 根据具体情况,由同学们自己选取C语言的一个适当大小的子集(可取一类典型单词,也可以尽可能使各种类型的单词都兼顾到);在实习前一定要制出相应的表。
l 实验时间:4-8学时
l 检查内容及时间:A)完整的实验报告。
B)在机器上调试成功的源程序。
3、 软件、硬件环境
l Visual studio 2010 ,Windows 7操作系统
l 计算机一台
2.实验步骤
l 分析
对于单词符号我们将其分成四类:保留字K、标识符I、常数C和界符P,每类单词符号均可使用一张表格表示.在词法分析过程中,保留字K和界符P这两个表格的内容是固定不变的(由语言确定),源程序字符串只能从其中选取,而标识符I、常数C这两表是在分析过程中不断形成的.
对于一个具体源程序而言,在扫描字符串时识别出一个单词,若这个单词的类型是K、I、C或P中之一,那么就以单词的二元式形式输出.每次调用词法分析程序,它均能自动继续扫描下去,形成下一个单词,直到整个源程序全部扫描完毕,从而形成相应的单词串.
各类单词的二元式表述均具有相同的结构与长度,形式如下:
(单词种别t,单词自身的值i)
t是单词种别,而单词种别共分为K、I、C、P四类且每类对应一张表格.因此,t实际上就是一个指向这四类中某一类对应表格的指针.i则为指向该类表格中一个特定项目的指针.
所以整个的词法分析过程就是从源程序中获得一个个的单词符号,将这些符号分别填入四张类表中,并且有一个二元式序列构成一个索引,这个索引为以后的语法分析提供处理上的方便.
为了减少实习量,可以适量地选取K,P中的一个子集来进行.如下表:
表1 保留字K表
内部地址
1
2
3
4
5
6
7
8
9
10
保 留 字
for
while
DO
ELSE
IF
STATIC
INT
SIZEOF
BREAK
CONTINUE
表2 界符P表
内部地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
界 符
+
-
*
/
<
<=
>
>=
!=
==
=
(
)
,
:
;
{
}
表3 单词符号的编码
单词符号
类别编码
标识符
1
常数
2
保留字
3
+
4
-
5
*
6
/
7
<
8
<=
9
>
10
>=
11
!=
12
==
13
=
14
(
15
)
16
,
17
:
18
;
19
{
20
}
21
保留字表包括10个有代表性的保留字,界符表包括关系运算符,算术运算符,分隔符三种,一对圆括号,加上赋值号,花括号,分号,引号.这两表的内容表明C语言的条件语句,赋值语句,WHILE型循环语句,复合语句,过程及变量说明均可作为源程序例子输入给词法分析程序,标识符表I中的每一项包含一个标识符,常数表C中的每一项包含一个整常数,后两表的内容都是在词法分析过程中产生的.
如何从源程序中识别出一个个的单词符号呢?图1中的流图清晰地反映出这一过程.
图1中,双圆圈的状态表示终态,即能到达终态就代表识别出一个单词符号,而带有*号的终态是指处理时应回退一字符.
l 算法
词法分析器在扫描过程中,依次从源程序中取出源字符,根据图1的扫描过程状态转换图,当碰到终态时,即双圆圈的状态时就得到一个单词符号,此时可以根据第一个字符判断单词属于K,I,C,P中哪一个类,从而确定单词的“单词种别”和“单词自身的值”。
2
字母
非字母与数字
1
字母与数字
0
空白
4
数字
非数字
3
数字
+
6
;
5
8
:
非=
7
=
9
(
10
,
11
)
12
其它
17
14
<
非=
13
15
>
16
=
图1 扫描程序的状态转换图
说明:这个图只是大概表达一个词法分析的思路,由于不知道加入,所以并不完全准确。
2. 实验内容
流程图 、程序
其它
界符
数字
字母
出错处理
有
C表有此单词否?
形成一项
无
形成(C,i)
十进制数转化成二进制数
K表有此单词否?
形成(I,i)
形成(K,i)
有
无
查界符表
形成(P,i)
源程序完否?
输出二元式
开始
开始
对源程序扫描
扫描到的单词符号
首字母是什么?
完
未完
图2词法分析算法流图
源程序
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <assert.h>
#define LENGTH1 10 //定义保留字的大小
FILE * fp=NULL; //输出流指针
FILE * fw=NULL; //输入流指针
char character; //字符
char token[16]; //字符数组,用来存放已读入的字符序列
//编码表
char* CODE[]={"identifier"/*标识符*/,"constant"/*常数*/,"keyword"/*保留字*/,"+","-",
"*","/","<","<=",">",">=","!=","==","=","(",")",",",":",";","{","}"};
//保留字表
char *k[]={"for","while","do","else","if","static","int","sizeof","break","continue"};
//标识符结构体
typedef struct
{
char * I[256]; //标识符数组
int len;//标识符数量
} identifier;
//常量结构体
typedef struct {
int cont[300];//存放常量的数组
int len;//常量的数目
} constnumber;
//读入一个字符,从输入流中读入一个字符到变量character中。
void getNextChar(FILE *ifp)
{
if((character=getc(ifp))==EOF)
exit(1);
}
//读入非空白字符,检查变量character中的字符是否为空白字符或回车或换行符。若是,
//则调用getNextChar()读入下一个字符,直到character中的字符满足条件.
void getnbc(FILE *ifp)
{
while(character==' '|| character=='\n'||character==9)
{
getNextChar(ifp);
}
}
//连接字符串,把character中的字符连接到token数组的结尾。
void concat()
{
char * ct=&character;
strcat(token,ct);
}
//判断是否为字母。
int letter()
{
return isalpha(character);
}
//判断是否为数字
int digit()
{
return isdigit(character);
}
//回退字符,将刚读入的character中的字符回退到输入流中。并把character中的值置为空。
void retract(FILE *ifp)
{
(ifp->_cnt)++;
(ifp->_ptr)--;
character=' ';
}
//处理保留字,对存放在token中的字符串查保留字,若查到,则返回该保留字的类别编码,否则返回0.
int reserve(char **k)
{
int i;
for(i=0;i<LENGTH1;i++)
if(strcmp(token,k[i])==0)
return i+1;
return 0;
}
//处理标识符,对存放在token中的字符串查找符号表,若查到,则返回它在符号表的位置,
//存入常数表中,并返回它在常数表中的位置编号。
int symbol(identifier * id)
{
int i;
for(i=0;i<id->len;i++)
if(strcmp(token,id->I[i])==0)
return i+1;
if(id->len>256)
assert(0);
id->I[id->len]=token;
id->len++;
return id->len;
}
//将数字字符串转化为整数。
int strtonumber()
{
int i;
int sum=0;
for(i=0;i<strlen(token);i++)
{
sum=10*sum+(token[i]-'0');
}
return sum;
}
//常数存入常数表的函数,将token中的数字串(实际上是字符串),转化成标准的二进制值(整数值)
//存入常数表中,并返回它在常数表中的位置编号。
int constant(constnumber * con)
{
con->cont[con->len]=strtonumber();
con->len++;
return con->len;
}
//将整数值转化为字符串
char * numbertoString(int num)
{
char s[3];
int i=num/10;
while(i>0)
{
char c=i+'0';
strcat(s,&c);
}
return s;
}
//将结果写入到文件并且输出到屏幕。
void returntofile(int num,int val,identifier *id,constnumber *con)
{
int i;
int _num=num;
char c;
c='(';
putc(c,fw);
printf("%c",c);
i=_num/10;
while(i>0)
{
_num=_num-10*i;
c=(i+'0');
printf("%c",c);
putc(c,fw);
i=_num/10;
}
c=_num+'0';
printf("%c",c);
putc(c,fw);
printf(",");
putc(',',fw);
//如果是标识符或常数 则放入括号内。
if(num==1) //处理标识符
{
printf("%s",id->I[val-1]);
printf(")");
printf("\n");
fputs(id->I[val-1],fw);
putc(')',fw);
putc('\n',fw);
}
if(num==2) //处理常数
{
_num=con->cont[val-1];
i=_num/10;
while(i>0)
{
_num=_num-10*i;
c=(i+'0');
printf("%c",c);
putc(c,fw);
i=_num/10;
}
c=_num+'0';
printf("%c",c);
printf(")");
printf("\n");
putc(c,fw);
putc(')',fw);
putc('\n',fw);
}
if(num==3) //保留字
{
printf("-");
printf(")");
printf(" ");
printf("#");
printf("%s",k[val-1]);
printf("#");
printf("\n");
putc('-',fw);
putc(')',fw);
fputs(" ",fw);
putc('#',fw);
fputs(k[val-1],fw);
putc('#',fw);
putc('\n',fw);
}
if(num>3) //处理界符
{
printf("-");
printf(")");
printf(" ");
printf("#");
printf("%s",CODE[num-1]);
printf("#");
printf("\n");
putc('-',fw);
putc(')',fw);
fputs(" ",fw);
putc('#',fw);
fputs(CODE[num-1],fw);
putc('#',fw);
putc('\n',fw);
}
}
//将错误写入到文件或输出到屏幕
void error()
{
printf("(ERROR,");
printf("%c",character);
printf(")");
printf("\n");
fputs("(ERROR,",fw);
putc(character,fw);
putc(')',fw);
putc('\n',fw);
}
//词法分析函数
void LexAnalyze(char **k,char **CODE,identifier *id,constnumber *con,FILE *fp,FILE *fw)
{
int num,val;
strcpy(token,"");
getNextChar(fp);
getnbc(fp);
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':
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())
{
concat();
getNextChar(fp);
}
retract(fp);
num=reserve(k);//保留字
if(num!=0)
returntofile(3,num,id,con);
else
{
val=symbol(id);
returntofile(1,val,id,con);
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
while(digit())
{
concat();
getNextChar(fp);
}
retract(fp);
val=constant(con);
returntofile(2,val,id,con);
break;
case '<':
getNextChar(fp);
if(character=='=')
returntofile(9,0,id,con);
else
{
retract(fp);
returntofile(8,0,id,con);
}
break;
case '>':
getNextChar(fp);
if(character=='=')
returntofile(11,0,id,con);
else
{
retract(fp);
returntofile(10,0,id,con);
}
break;
case '=':
getNextChar(fp);
if(character=='=')
returntofile(13,0,id,con);
else
{
retract(fp);
returntofile(14,0,id,con);
}
break;
case '!':
getNextChar(fp);
if(character=='=')
returntofile(12,0,id,con);
else
error();
break;
case '+':
returntofile(4,0,id,con);
break;
case '-':
returntofile(5,0,id,con);
break;
case '*':
returntofile(6,0,id,con);
break;
case '/':
returntofile(7,0,id,con);
break;
case '(':
returntofile(15,0,id,con);
break;
case ')':
returntofile(16,0,id,con);
break;
case ',':
returntofile(17,0,id,con);
break;
case ':':
returntofile(18,0,id,con);
break;
case ';':
returntofile(19,0,id,con);
break;
case '{':
returntofile(20,0,id,con);
break;
case '}':
returntofile(21,0,id,con);
break;
default:
error();
}
}
main(int argc,char *argv[])
{
//初始化标识符和常数结构体
identifier* id=(identifier*)malloc(sizeof(identifier));
constnumber * con=(constnumber*)malloc(sizeof(constnumber));
con->len=0;
id->len=0;
argc=3;
argv[1]="E:\\file1.txt";//待分析的文件
argv[2]="E:\\file2.txt";//保存分析结果的文件
//从打开目标文件流
if((fp=fopen(argv[1],"r"))==NULL)
{
printf("cat: can't open %s\n",*argv);
return 1;
}
//打开要写二元式的文件流
if((fw=fopen(argv[2],"w"))==NULL)
{
printf("cat:can't open %s\n",argv[2]);
return 1;
}
while(!feof(fp))
{
LexAnalyze(k,CODE,id,con,fp,fw);//执行词法分析
}
//关闭流
fclose(fp);
fclose(fw);
return 0;
}
4.实验结果
要分析的C语言程序(1)
int i=0,sum=0;
while(i<10)
{
sum=sum+i;
i=i+1;
}
结果为:
(3,-) #int#
(1,i)
(14,-) #=#
(2,0)
(17,-) #,#
(1,sum)
(14,-) #=#
(2,0)
(19,-) #;#
(3,-) #while#
(15,-) #(#
(1,i)
(8,-) #<#
(2,10)
(16,-) #)#
(20,-) #{#
(1,sum)
(14,-) #=#
(1,sum)
(4,-) #+#
(1,i)
(19,-) #;#
(1,i)
(14,-) #=#
(1,i)
(4,-) #+#
(2,1)
(19,-) #;#
(21,-) #}#
要分析的源程序(2)
char s[3];
int i=num/10;
while(i>0)
{
char c=i+'0';
strcat(s,&c);
}
return s;
结果为:
(1,char)
(1,s)
(ERROR,[)
(2,3)
(ERROR,])
(19,-) #;#
(3,-) #int#
(1,i)
(14,-) #=#
(1,num)
(7,-) #/#
(2,10)
(19,-) #;#
(3,-) #while#
(15,-) #(#
(1,i)
(10,-) #>#
(2,0)
(16,-) #)#
(20,-) #{#
(1,char)
(1,c)
(14,-) #=#
(1,i)
(4,-) #+#
(ERROR,')
(2,0)
(ERROR,')
(19,-) #;#
(1,strcat)
(15,-) #(#
(1,s)
(17,-) #,#
(ERROR,&)
(1,c)
(16,-) #)#
(19,-) #;#
(21,-) #}#
(1,return)
(1,s)
(19,-) #;#
5. 实验总结分析
上机前应做好准备.即根据实习目的、要求和分析,选择相应的数据结构,使用C语言参照算法中的流程编写词法分析的程序.将编好的程序上机进行调试.注意调试的例子应有词法正确的,也应有词法错误的或是超出所选数据结构范围的.
现在我谈一下关于做这个词法分析程序的心得。这个词法分析程序是全部用C语言的语法写的,幸亏我以前学过C语言。我认为C语言是很好的语言。这次写这个程序算是对我所学的C语言的一次的检验。在写程序的过程中,也遇到了一些问题,最主要的是文件的输入与输出,最头疼的是指针。几乎所在的错误都与指针有关。在遇到问题以后,我们不要畏惧,我们可以通过程序调试,找出问题的所在,只要找到问题的源,就总在解决它的办法。
在这个分析程序中,我加了注释,是很容易看懂的,而且程序的功能模块也分得很清楚。在写大型的程序时,这两点我认为是非常重要的。
附录资料:不需要的可以自行删除
Abstract: Based on the comprehensive analysis on the plastic part’s structure service requirement, mounding quality and mould menu factoring cost. A corresponding injection mould of internal side core pulling was designed. By adopting the multi-direction and multi-combination core-pulling. A corresponding injection mould of internal side core pulling was designed, the working process of the mould was introduced
C语言详解 - 枚举类型
注:以下全部代码的执行环境为VC++ 6.0
在程序中,可能需要为某些整数定义一个别名,我们可以利用预处理指令#define来完成这项工作,您的代码可能是:
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
在此,我们定义一种新的数据类型,希望它能完成同样的工作。这种新的数据类型叫枚举型。
1. 定义一种新的数据类型 - 枚举型
以下代码定义了这种新的数据类型 - 枚举型
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
(1) 枚举型是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开。
(2) DAY是一个标识符,可以看成这个集合的名字,是一个可选项,即是可有可无的项。
(3) 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。
(4) 可以人为设定枚举成员的值,从而自定义某个范围内的整数。
(5) 枚举型是预处理指令#define的替代。
(6) 类型定义以分号;结束。
2. 使用枚举类型对变量进行声明
新的数据类型定义完成后,它就可以使用了。我们已经见过最基本的数据类型,如:整型int, 单精度浮点型float, 双精度浮点型double, 字符型char, 短整型short等等。用这些基本数据类型声明变量通常是这样:
char a; //变量a的类型均为字符型char
char letter;
int x,
y,
z; //变量x,y和z的类型均为整型int
int number;
double m, n;
double result; //变量result的类型为双精度浮点型double
既然枚举也是一种数据类型,那么它和基本数据类型一样也可以对变量进行声明。
方法一:枚举类型的定义和变量的声明分开
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY yesterday;
enum DAY today;
enum DAY tomorrow; //变量tomorrow的类型为枚举型enum DAY
enum DAY good_day, bad_day; //变量good_day和bad_day的类型均为枚举型enum DAY
方法二:类型定义与变量声明同时进行:
enum //跟第一个定义不同的是,此处的标号DAY省略,这是允许的。
{
saturday,
sunday = 0,
monday,
tuesday,
wednesday,
thursday,
friday
} workday; //变量workday的类型为枚举型enum DAY
enum week { Mon=1, Tue, Wed, Thu, Fri Sat, Sun} days; //变量days的类型为枚举型enum week
enum BOOLEAN { false, true } end_flag, match_flag; //定义枚举类型并声明了两个枚举型变量
方法三:用typedef关键字将枚举类型定义成别名,并利用该别名进行变量声明:
typedef enum workday
{
saturday,
sunday = 0,
monday,
tuesday,
wednesday,
thursday,
friday
} workday; //此处的workday为枚举型enum workday的别名
workday today, tomorrow; //变量today和tomorrow的类型为枚举型workday,也即enum workday
enum workday中的workday可以省略:
typedef enum
{
saturday,
sunday = 0,
monday,
tuesday,
wednesday,
thursday,
friday
} workday; //此处的workday为枚举型enum workday的别名
workday today, tomorrow; //变量today和tomorrow的类型为枚举型workday,也即enum workday
也可以用这种方式:
typedef enum workday
{
saturday,
sunday = 0,
monday,
tuesday,
wednesday,
thursday,
friday
};
workday today, tomorrow; //变量today和tomorrow的类型为枚举型workday,也即enum workday
注意:同一个程序中不能定义同名的枚举类型,不同的枚举类型中也不能存在同名的命名常量。错误示例如下所示:
错误声明一:存在同名的枚举类型
typedef enum
{
wednesday,
thursday,
friday
} workday;
typedef enum WEEK
{
saturday,
sunday = 0,
monday,
} workday;
错误声明二:存在同名的枚举成员
typedef enum
{
wednesday,
thursday,
friday
} workday_1;
typedef enum WEEK
{
wednesday,
sunday = 0,
monday,
} workday_2;
3. 使用枚举类型的变量
3.1 对枚举型的变量赋值。
实例将枚举类型的赋值与基本数据类型的赋值进行了对比:
方法一:先声明变量,再对变量赋值
#include<stdio.h>
/* 定义枚举类型 */
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
void main()
{
/* 使用基本数据类型声明变量,然后对变量赋值 */
int x, y, z;
x = 10;
y = 20;
z = 30;
/* 使用枚举类型声明变量,再对枚举型变量赋值 */
enum DAY yesterday, today, tomorrow;
yesterday = MON;
today = TUE;
tomorrow = WED;
printf("%d %d %d \n", yesterday, today, tomorrow);
}
方法二:声明变量的同时赋初值
#include <stdio.h>
/* 定义枚举类型 */
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
void main()
{
/* 使用基本数据类型声明变量同时对变量赋初值 */
int x=10, y=20, z=30;
/* 使用枚举类型声明变量同时对枚举型变量赋初值 */
enum DAY yesterday = MON,
today = TUE,
tomorrow = WED;
printf("%d %d %d \n", yesterday, today, tomorrow);
}
方法三:定义类型的同时声明变量,然后对变量赋值。
#include <stdio.h>
/* 定义枚举类型,同时声明该类型的三个变量,它们都为全局变量 */
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN
展开阅读全文