实验一 源程序的预处理及词法分析程序设计
一、 实验目的
设计并实现一个包含预处理功能的词法分析程序,加深对编译中词法分析过程的理解。
二、 实验要求
1、实现预处理功能
源程序中可能包含有对程序执行无意义的符号,要求将其剔除。
首先编制一个源程序的输入过程,从键盘、文件或文本框输入若干行语句,依次存入输入缓冲区(字符型数据);然后编制一个预处理子程序,去掉输入串中的回车符、换行符和跳格符等编辑性文字;把多个空白符合并为一个;去掉注释。
2、实现词法分析功能
输入:所给文法的源程序字符串。
输出:二元组(syn,token或sum)构成的序列。其中,
syn为单词种别码。
Token为存放的单词自身字符串。
Sum为整型常量。
具体实现时,可以将单词的二元组用结构进行处理。
3、待分析的C语言子集的词法
1)关键字
main if then while do
static int double struct
break else long switch
case typedef char return
const float short continue
for void sizeof
所有的关键字都是小写。
2)运算符和界符
- * / + = < <= > >= ; ( ) #
3)其他标记ID和NUM
通过以下正规式定义其他标记:
ID→letter(letter|digit)*
NUM→digit digit*
letter→a|…|z|A|…|Z
digit→0|…|9…
4)空格由空白、制表符和换行符组成
空格一般用来分隔ID、NUM、专用符号和关键字,词法分析阶段通常被忽略。
4、各种单词符号对应的种别码
表1 各种单词符号的种别码
单词符号 种别码 单词符号 种别码
main 1 ; 41
if 2 ( 42
then 3 ) 43
while 4 int 7
do 5 double 8
static 6 struct 9
ID 25 break 10
NUM 26 else 11
+ 27 long 12
- 28 switch 13
* 29 case 14
/ 30 typedef 15
** 31 char 16
== 32 return 17
< 33 const 18
!= 34 float 19
<= 35 short 20
> 36 continue 21
>= 37 for 22
= 38 void 23
[ 39 sizeof 24
] 40 # 0
++ 44 -- 45
{ 46 } 47
, 48 & 49
% 50
5、 词法分析程序的主要算法思想
算法的基本任务是从字符串表示的源程序中识别出具有独立意义的单词符号,其基本思想是根据扫描到的单词符号的第一个字符的种类,拼出相应的单词符号。
其中初值包括如下两方面:
(1) 关键字表初值
关键字作为特殊标识符处理,把它们预先安排在一张表格中(称为关键字表),当扫描程序识别出标识符时,查关键字表。如能查到匹配的单词,则该单词为关键字,否则为一般标识符。关键字表为一个字符串数组,其描述如下:
char *rwtab[27]={“main”,”if”,”then”,”while”,”do”,”static”,”int”,”double”,”struct”,”break”,”else”,”long”,”switch”,”case”,”typedef”,”char”,”return”,”const”,”float”,”short”,”continue”,”for”,”void”,”sizeof” };
(2) 程序中需要用到的主要变量:syn,token和sum。
代码如下:
#include <bits/stdc++.h>
using namespace std;
//@author lyj
#define N 10005
string rwtab[27]={" ","main","if","then","while","do",
"static","int","double","struct","break",
"else","long","switch","case","typedef",
"char","return","const","float","short",
"continue","for","void","sizeof"};//关键字表
//预处理
void init(){
//判断测试源文件是否存在
FILE *p1,*p2;
if((p1=fopen("testinput.txt","r"))==NULL){
cout<<"栓Q!文件不存在或文件错误!"<<endl;
}
//读入测试文件中的字符
int i=0,j=0;
char str[N],str1[N],c;
while((c=fgetc(p1))!=EOF){
str[i]=c;
i++;
}
fclose(p1);
str[i]='\0';
//进行处理
for(i=0;i<strlen(str);i++){
//判断单行注释,处理
if(str[i]=='/'&&str[i+1]=='/'){
while(str[i]!='\n'){
i++;
}
}
//判断多行注释,处理
else if(str[i]=='/'&&str[i+1]=='*'){
while(!(str[i]=='*'&&str[i+1]=='/')){
i++;
}
i+=2;
}
//判断多个空格,处理
else if(str[i]==' '&&str[i+1]==' '){
while(str[i]==' '){
i++;
}
i--;
if(str1[j-1]!=' '){
str1[j++]=' ';
}
}
//判断回车、换行、跳格等编辑性字符,处理
else if(str[i]=='\r'||str[i]=='\n'||str[i]=='\t'){
if(str1[j-1]!=' '){
str1[j++]=' ';
}
}
//其他字符处理
else
str1[j++]=str[i];
}
str1[j]='\0';
//将预处理后得到的字符存储到文本文件
if((p2=fopen("testoutput.txt","w"))==NULL){
cout<<"预处理文件存储失败!"<<endl;
}
puts(str1);
fputs(str1,p2);
fclose(p2);
}
//扫描子程序
void scanner(){
FILE *p1,*p2;
int syn,sum;
char token[200];
char str[N];
char c;
int i=0;
int j=0;
int k;
//打开预处理后的文件,读取字符
if((p1=fopen("testoutput.txt","r"))==NULL){
cout<<"栓Q!文件不存在!"<<endl;
}
while((c=fgetc(p1))!=EOF){
str[i]=c;
i++;
}
fclose(p1);
str[i]='\0';
//创建结果文件
if((p2=fopen("analysis.txt","w"))==NULL){
cout<<"栓Q!出错了!"<<endl;
}
//开始扫描
for(i=0;str[i]!='\0';){
j=0;
sum=-1;//标志非数字
//处理关键字、标识符
if((str[i]>='a'&&str[i]<='z')||(str[i]>='A'&&str[i]<='Z')||str[i]=='_'){
while((str[i]>='a'&&str[i]<='z')||(str[i]>='A'&&str[i]<='Z')||(str[i]>='0'&&str[i]<='9')||str[i]=='_'){
token[j++]=str[i++];
}
token[j]='\0';
for(k=1;k<27;k++){
if(rwtab[k]==token)
break;//跳出循环时,k=关键字的种别码
}
if(k<=24){
syn=k;
}
else{
syn=25;//非关键字,种别码为25
}
}
//以数字开头的非法标识符处理
else if((str[i]>='0'&&str[i]<='9')&&((str[i+1]>='a'&&str[i+1]<='z')|| (str[i+1]>='A'&&str[i+1]<='Z')||str[i+1]=='_')){
sum=-1;
syn = -1;//标志非法标识符
while((str[i]>='a'&&str[i]<='z')||(str[i]>='A'&&str[i]<='Z')||str[i]=='_'||(str[i]>='0'&&str[i]<='9')){
token[j++] = str[i++];
}
i--;
}
//数字处理
else if(str[i]>='0'&&str[i]<='9') {
while(str[i]>='0'&&str[i]<='9'){
token[j++]=str[i++];
}
token[j]='\0';
sum=atoi(token);//字符串转换为数字
if(sum>=0)
syn=26;
}
//空格的处理
else if(str[i]==' '){
i++;//遇到空格跳过
syn=-2;//空格标志
}
//运算符、界符等处理
else{
//连续两个符号组成的运算符
if(str[i]=='+'&&str[i+1]=='+'){
syn=44;
token[j++]=str[i++];
token[j++]=str[i++];
}
else if(str[i]=='-'&&str[i+1]=='-'){
syn=45;
token[j++]=str[i++];
token[j++]=str[i++];
}
else if(str[i]=='*'&&str[i+1]=='*'){
syn=31;
token[j++]=str[i++];
token[j++]=str[i++];
}
else if(str[i]=='='&&str[i+1]=='='){
syn=32;
token[j++]=str[i++];
token[j++]=str[i++];
}
else if(str[i]=='!'&&str[i+1]=='='){
syn=34;
token[j++]=str[i++];
token[j++]=str[i++];
}
else if(str[i]=='<'&&str[i+1]=='='){
syn=35;
token[j++]=str[i++];
token[j++]=str[i++];
}
else if(str[i]=='>'&&str[i+1]=='='){
syn=37;
token[j++]=str[i++];
token[j++]=str[i++];
}
//单个运算符号及界符等
else{
switch(str[i]){
case '+':syn=27;break;
case '-':syn=28;break;
case '*':syn=29;break;
case '/':syn=30;break;
case '<':syn=33;break;
case '>':syn=36;break;
case '=':syn=38;break;
case '[':syn=39;break;
case ']':syn=40;break;
case ';':syn=41;break;
case '(':syn=42;break;
case ')':syn=43;break;
case '{':syn=46;break;
case '}':syn=47;break;
case ',':syn=48;break;
case '&':syn=49;break;
case '%':syn=50;break;
case '#':syn=0;break;
default:syn=-3;break;
}
token[j++]=str[i++];
}
token[j]='\0';
}
//输出结果
if(sum!=-1){
printf("%d : %d\n",sum,syn);//输出数字
fprintf(p2,"%d : %d\n",sum,syn);
}
else if(syn==-1){
printf("栓Q!%s 是非法标识符\n",token);//输出非法标识符
fprintf(p2,"栓Q!%s 是非法标识符\n",token);
}
else if(syn==-3){
printf("%s :栓Q!无法识别它!\n",token);//输出无法识别的
fprintf(p2,"%s :栓Q!无法识别它!\n",token);
}
else if(syn!=-1&&syn!=-2&&syn!=-3){
printf("%s : %d\n",token,syn);//输出可以识别的
fprintf(p2,"%s : %d\n",token,syn);
}
}
fclose(p2);
}
int main(){
//进行预处理
init();
//调用扫描子程序
scanner();
}
测试数据
结果