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

词法分析器的主要功能是把源代码整理成一个个记号(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)。这个问题让我找了两个小时的错误!
都到了崩溃的边缘了。~.~!


《完》

  • 5
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值