您的位置:首页 > 编程语言 > C语言/C++

编译原理--C-Minus词法分析器C++实现

2016-04-25 14:57 344 查看
词法分析器的主要功能是把源代码整理成一个个记号(token),记号的类型主要有系统保留字(if,return等)、特殊字符(+,*,/等)、字符串记号(数字和标志符)。

如:str[i] = 45 + 6;

将上面代码整理:

标识符:str,i

特殊符:[ , ],+

数字 : 45,6

DFA如下:



c-测试源代码:

/* A program to perform Euclid's
Algorithm to compute gcd. */

int gcd (int u, int v)
{
if (v == 0)
return u ;
else
return gcd(v,u-u/v*v);
/* u-u/v*v == u mod v */
}

void main(void)
{
int x; int y;
x = input();
y = input();
output(gcd(x,y));
}


代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<iostream>
#define sfName "source.c"
#define tfName "target.c"
typedef int sInt;
using namespace std;

FILE *source;
FILE *target;

long beginp[5];   //记录文件指针的开始位置
long endp[5];    //记录文件指针的结束位置
char idStr[80]="";   //保存标识符中间值
int state[5]={0}; //不同记号类型的状态
char unaryOP[16]={'+','-','*','/','<','>','=',';',',','(',')','[',']','{','}','!'}; //保存一元运算符
char *p[6]={"if","else","int","return","void","while"};  //系统保留字
char *strToken[7]={"ID","NUM","OP","FUCN","RESW","ERROR","COM"};
typedef enum {ID,NUM,OP,FUCN,RESW,ERROR,COM}tokenType; //记号类型

void clearState()
{
memset(state,0,sizeof(state));
memset(idStr,'\0',sizeof(idStr));
memset(beginp,0,sizeof(beginp));
memset(endp,0,sizeof(endp));
}

void strPrintf(long begin,long end,tokenType t)
{
bool isComment = false;
int k;
char s[200]="";
long len=end-begin;
//printf("%d %d\n",begin,end);
fseek(source,-len,1);//文件指针回退
for(int i=0;i<len;i++)
{
s[i]=fgetc(source);
}
//fseek(source,len,1); //文件指针归位
switch(t)
{
case ID:
k=0;break;
case NUM:
k=1;break;
case OP:
k=2;break;
case FUCN:
k=3;break;
case RESW:
k=4;break;
case ERROR:
k=5;break;
case COM:
k=6;
isComment=true;break;
default:cout<<"输入有误!\n";
}
//printf("%s\n",s);
if(isComment)
{
fprintf(target,"%s\n",s);
printf("%s\n",s);
}

else
{
fprintf(target,"<%s,%s>\n",strToken[k],s);
printf("<%s,%s>\n",strToken[k],s);
}
}

void unaryPrintf(char s,tokenType t)
{
int k;
switch(t)
{
case ID:
k=0;break;
case NUM:
k=1;break;
case OP:
k=2;break;
case FUCN:
k=3;break;
case RESW:
k=4;break;
case ERROR:
k=5;break;
case COM:
k=6;break;
default:cout<<"输入有误!\n";
}
//cout<<strToken[k]<<endl;
fprintf(target,"<%s,%c>\n",strToken[k],s);
printf("<%s,%c>\n",strToken[k],s);
}

bool isOperator(char ch)
{
for(int i=0;i<16;i++)
{
if( ch==unaryOP[i] )
return true;
}
return false;
}

void comment(char ch,int i)
{
bool isexit = false; //当不是注释时用于跳出循环
//tokenType t=COM;
while(!feof(source))
{
switch(state[i])
{
case 0:
if(ch=='/')
{
state[i]=1;
beginp[i]=ftell(source)-1;
}
break;
case 1:
if(ch=='*')
{
state[i]=2;
}
else
{
isexit = true; //说明不是注释,请求退出
unaryPrintf('/',OP);
fseek(source,-1L,1); //回退一个字节,因为向后判断移了一位
}
break;
case 2:
if(ch=='*')
{
state[i]=3;
}else
{
state[i]=2;
}
break;
case 3:
if(ch=='/')
{
state[i]=4;
endp[i]= ftell(source);
strPrintf( beginp[i],endp[i],COM);
isexit = true;                               //back
}else state[i]=2;
break;
}
if(isexit) return;//back;
ch = fgetc(source);
}
}

void number(char ch,int i)
{
beginp[i]=ftell(source)-1;
while(!feof(source))
{
if(ch>='0' && ch<='9')
{
state[i]=1;
endp[i]=ftell(source);
}else
{
fseek(source,-1L,1); //回退,读到了下一个字符
strPrintf(beginp[i],endp[i],NUM);
return;
}
ch = fgetc(source);
}
}

void myOperator(char ch,int i)
{
bool isReturn = false;
while(!feof(source))
{
if(ch=='+' ||ch=='-'||ch=='*'||ch==';'||ch==','||ch=='('||ch==')'||ch=='['||ch==']'||ch=='{'||ch=='}')
{
state[i]=1;
unaryPrintf(ch,OP);
isReturn = true;
}else
{
switch(state[i])
{
case 0:
beginp[i]=ftell(source)-1;
switch(ch)
{
case '<':
state[i]=2;
break;
case '>':
state[i]=4;
break;
case '=':
state[i]=6;
break;
case '!':
state[i]=8;
break;
default:cout<<"data error!\n";
}
break;
case 2:
if(ch=='=')
{
state[i]=3;
endp[i]=ftell(source);
strPrintf(beginp[i],endp[i],OP);
isReturn = true;
}else
{
//属于一元操作符
state[i]=2;
fseek(source,-1L,1);//回退一个字符
unaryPrintf('<',OP);
isReturn = true;
}
break;
case 4:
if(ch=='=')
{
state[i]=5;
endp[i]=ftell(source);
strPrintf(beginp[i],endp[i],OP);
isReturn = true;
}else
{
//属于一元操作符
state[i]=4;
fseek(source,-1L,1);//回退一个字符
unaryPrintf('>',OP);
isReturn = true;
}
break;
case 6:
if(ch=='=')
{
state[i]=7;
endp[i]=ftell(source);
strPrintf(beginp[i],endp[i],OP);
isReturn = true;
}else
{
//属于一元操作符
state[i]=6;
fseek(source,-1L,1);//回退一个字符
unaryPrintf('=',OP);
isReturn = true;
}
break;
case 8:
if(ch=='=')
{
state[i]=9;
endp[i]=ftell(source);
strPrintf(beginp[i],endp[i],OP);
isReturn = true;
}else
{
//属于一元操作符
state[i]=8;
fseek(source,-1L,1);//回退一个字符
unaryPrintf('!',OP);
isReturn = true;
}
break;
default:cout<<"data error!\n";
}
}
if(isReturn) return;
ch = fgetc(source);
}
}
bool isLiter(char ch)
{
if((ch>='A' && ch<='Z')||( ch>= 'a' && ch<='z') || ch=='_')
{
return true;
}else
return false;
}

bool isResw(char *s)
{
for(int i=0;i<6;i++)
{
if( strcmp(s,p[i])==0 )
return true;
}
return false;
}

void identifier(char ch,int i)
{
beginp[i] = ftell(source)-1;
long len =0;
bool isQuit = false;
bool isFucn = false;
while(!feof(source))
{
if( isLiter(ch) )
{
state[i]=1;
endp[i]=ftell(source);
}
else
{
long enter = 1; //
isQuit = true;
if(ch=='\n') enter = 2;
fseek(source,-enter,1); //回退一或2个字符
//cout<<"pos="<<ftell(source)<<endl;
//printf("%d %d\n",beginp[i],endp[i]);
len = endp[i]-beginp[i];
fseek(source,-len,1);
//cout<<"pos="<<ftell(source)<<endl;
for(int j=0;j<len;j++)
{
idStr[j] = fgetc(source);

}
if( isResw(idStr) ) //如果是保留字,就保存退出
{
strPrintf(beginp[i],endp[i],RESW);

}else
{
char temps;
long cout=1;
temps = fgetc(source);
while(!feof(source))
{
if(temps==' ' ||temps=='\n' || temps=='\t')
{
; //jump it;
}else
{
if(temps=='(')
{
isFucn = true; //表明是函数名
break;
}
else
{
isFucn = false; //不是函数名
break;
}
}
temps = fgetc(source);
cout++;
}
fseek(source,-cout,1); //回退文件指针
//printf("back=%ld\n",ftell(source));
if(isFucn)
{
strPrintf(beginp[i],endp[i],FUCN);
}else
{
strPrintf(beginp[i],endp[i],ID);
}
}
}
if(isQuit) return;
ch = fgetc(source);
}
}

void startScanner()
{
char ch=fgetc(source);
while( !feof(source) )
{
if(ch==' ' ||ch=='\n' || ch=='\t')
{
;//nothing jump it!
}else if(ch=='/')
{
comment(ch,0);
clearState(); //清楚状态信息
}else if(ch>='0'&&ch<='9')
{
number(ch,1); //处理数字
clearState();
}else if( isOperator(ch) )
{
myOperator(ch,2); //处理操作符
clearState();
}else if( isLiter(ch) )
{
identifier(ch,3); //处理标志符
clearState();
}
else
{
unaryPrintf(ch,ERROR);
}
ch = fgetc(source);
}
}

int main()
{
if((source=fopen(sfName,"r"))==NULL)
{
printf("文件打开失败!\n");
exit(0);
}
if((target=fopen(tfName,"w"))==NULL)
{
fprintf(stderr,"文件写入失败!\n");
exit(0);
}
startScanner();//开始扫描文件 entrance
fclose(source);
fclose(target);
return 0;
}


实现小结:其实写词法分析器的DFA并不难,使用ifelse嵌套或者switch选择嵌套都可以,我觉得容易出现问题是对文本的读取,以上代码基本使用的是c的文件库函数实现。

需要注意的是:计算机从ASCII文件(文本文件)读入字符时,遇到回车换行符(两个字符‘\r,‘\n’)时,系统把它转化为一个字符’\n’。所以用户只能收到’\n’,不能收到’\r’。但是使用函数fseek()时要格外小心。

看如下代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#define fname "target.c"
#define filename "hiluo.txt"
using namespace std;

FILE *fp;
void solution()
{

char ch;
long pos;
if((fp=fopen(filename,"r"))==NULL)
{
printf("open file error\n");
exit(0);
}
ch = fgetc(fp); //接受第一个字符
while(!feof(fp))
{
printf("%c",ch); //打印文本文件
ch=fgetc(fp);
}
printf("\n");
printf("\n");
rewind(fp);//将文件指针指向文件头
printf("文件的起始位置 = %ld\n",ftell(fp));
ch =fgetc(fp);
while(ch!='\n')
{
printf("%c",ch);  //输出换行符之前的字符
ch = fgetc(fp);
}
putchar(10);
pos = ftell(fp); //文件指针当前位置
ch = fgetc(fp);
printf("position = %ld\n",pos);
printf("当前字符 = '%c'\n",ch);
fclose(fp);
putchar(10);
}

int main()
{
solution();
//cout << "Hello world!" << endl;
return 0;
}




可以看到 ‘h’属于第五个字符,从0开始的h看似应该是第4个字符啊!

原来ftell()函数 并没有把’\r’忽略掉!这样编程就会遇到问题。

如文件指针回退 ,假如当前字符为’e’,回退到’c’,应该是fseek(fp,-4L,1)而不是fseek(fp,-3L,1)。这个问题让我找了两个小时的错误!

都到了崩溃的边缘了。~.~!

《完》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: