编译原理——用C++模拟一个词法分析器

一、需求分析

   当今时代是飞速发展的信息时代,随着计算机技术的普及与网络的广泛应用,计算机语言更显得至关重要。计算机语言是人与计算机之间传递信息的媒介。计算机系统最大特征是指令通过一种语言传达给机器。为了使电子计算机进行各种工作,就需要有一套用以编写计算机程序的数字、字符和语法规划,由这些字符和语法规则组成计算机各种指令(或各种语句)。这些就是计算机能接受的语言。一个高级语言程序的实现,必须依赖于相应的编译系统。编译程序的基本任务是将源语言程序翻译成等价的目标语言程序。

  词法分析阶段是编译过程的第一个阶段,是编译的基础。词法分析结果的好坏将直接影响中文信息处理上层应用的效果。理解词法分析在编译程序中的作用,加深对有穷自动机模型的理解,掌握词法分析程序的实现方法和技术,加深对编译原理的理解,掌握编译程序的实现方法和技术至关重要。

  本次项目则为简单的词法分析器,通过设计编制调试一个具体的词法分析程序,加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。具体方法为:编制一个读单词的程序,从输入的源程序中,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符和分隔符五大类。并依次输出各个单词的内部编码及单词符号自身值。(遇到错误时可显示“Error”,然后跳过错误部分继续显示)

 词法分析器相关知识:

 

1.词法分析器的功能和输出格式

词法分析器的功能是输入源程序,输出单词符号。词法分析器的单词符号常常表示成以下的二元式(单词种别码,单词符号的属性值)。在本实验中,采用的是一类符号一种别码的方式。

标识符的BNF表示:

<标识符>-> <字母><字母数字串>

<字母数字串>-><字母><字母数字串>|<数字><字母数字串>|ε

无符号整数的BNF表示:

<无符号整数>-> <数字><数字串>

<数字串>-> <数字><数字串> |ε

运算符的BNF表示:

<加法运算符>-> +

<减法运算符>-> -

<大于关系运算符>-> >

<大于等于关系运算符>-> >=

2.超前搜索

词法分析时,常常会用到超前搜索方法。如当前待分析字符串为“a>i”,当前字符为’>’,此时,分析器到底是将其分析为大于关系运算符还是大于等于关系运算符呢?显然,只有知道下一个字符是什么才能下结论。于是分析器读入下一个字符’+’,这时可知应将’>’解释为大于运算符。但此时,超前读了一个字符’i’,所以要回退一个字符,词法分析器才能正常运行。在分析标识符,无符号整数等时也有类似情况。

 

二、系统设计

(一)系统中的数据定义

词法分析器的功能输入源程序,按照构词规则分解成一系列单词符号。单词是语言中具有独立意义的最小单位,包括关键字、标识符、运算符、界符和常数等。

 

 

 

  1. 识别保留字:auto       break    case     char        const      continue  
    default    do       double   else        enum       extern    
    float      for      goto     if          int        long      
    register   return   short    signed      sizeof     static  
    struct     switch   typedef  union       unsigned   void  
    volatile    while
  2. 运算符包括:+、-、*、/、=、>、<、>=、<=、!=
  3. 分隔符包括:‘/*’、‘//’、 () { } [ ] " "  ' 
  4. 常数为无符号整形数;
  5. 其它的都识别为标识符

 

输出:词法分析器所输出单词符号常常表示成如下的二元式:(单词,单词符号的属性值)

 标识符一般统归为一种。常数包括整数和浮点数两种。

关键字、运算符、界符可将其全体视为一种。

单词符号的属性是指单词符号的特性或特征。

 

(二)系统的概要设计

 

  词法分析器主要特点是不依靠语法,而只依靠词法,即处理一个单词时不依赖于外部单词的信息,因此词法分析器一般都很简单。当然,对某些语言在作词法分析时,在有些情况下不得不往前查看多个字符,有时还要做一些特殊处理,还有一些在词法分析中处理不了的,要留到语法分析中进行处理。

 

  本算法主要利用状态转换图生成一个词法分析器,对输入的程序进行词法分析,并将分析得到的单词造表。其中关键字表和界限符表的大小是由高级语言的子集决定的,可以用数组装载;而标识符表和常数表的大小取决于输入的待分析程序中的变量、过程名、常数的个数,所以要用指针组成动态链表来装载。当然为了方便,我们也把它定义成数组处理。

 

对于一段可能的输入代码,其结果在屏幕上显示如下:

( 1 , 无符号整数)

( begin , 关键字 )

( if , 关键字 )

( +, 运算符 )

( ; , 界符 )

( a , 普通标识符 )

关键字或标识符的判断:读入一串字符,将ASCII码在字母范围的字符存入数组中,将该数组与设置好的关键字比较,如果相等则输出是关键字,否则继续读入直至下一字符既非数字也非字母,输出为标识符;

数字的判断:若跟在字母后面则一起输出为标识符,否则输出为数字

界符、运算符的判断:直接判断其ASCII码

 

 

运行过程过程流程图:

 

编译原理——用C++模拟一个词法分析器_第1张图片

(三)系统的详细设计

 

1.词法分析程序打开源文件,读取文件内容,直至遇上’$’文件结束符,然后读取结束。

编译原理——用C++模拟一个词法分析器_第2张图片

 

2.对读取的文件进行预处理,从头到尾进行扫描,去除//和/*  */的内容,以及一些无用的、影响程序执行的符号如换行符、回车符、制表符等。但是千万注意不要在这个时候去除空格,因为空格在词法分析中有用,比如说int i=3;这个语句,如果去除空格就变成了“inti=3”,这样就失去了程序的本意,因此不能在这个时候去除空格。

编译原理——用C++模拟一个词法分析器_第3张图片

 

3.接下来就要对源文件从头到尾进行扫描了,从头开始扫描,主控程序主要负责系统建立一个文件保存四个表,这四个表分别存储关键字、运算符、界符、过滤符。而标识符和常数则用正则表达式判断。建立了多个布尔类,当系统读取代码时,用空格或制表符作为切词标志符,当遇到空格就输出之前检索的字符串进行判断(规定每个单词符号之间都有空格),判断字符串时,系统会通过顺序查找依次调用布尔类与之匹配来判断其属性并输出,如没有匹配成功,则说明所检索的字符进步串不合法,系统则会输出非法字符串。直到最后一个字符串匹配完毕之后系统结束

编译原理——用C++模拟一个词法分析器_第4张图片

(四)系统核心算法

1.查找关键字:

int searchReserve(char reserveWord[][20],char s[]) //查找保留字
{
    for(int i=0; i<32; i++)
    {
        if(strcmp(reserveWord[i],s)==0)//如果查询成功
        {
            return i+1;
            //返回种别码
        }
    }
    return -1;//查找失败,返回-1,即标识符
}

2.判断是否是字母:

bool IsLetter(char letter)//查找是否是字母
{
    if(letter >='a' && letter <='z' || letter >='A' && letter <='Z' || letter =='_')
    {
        return true;
    }
    else
    {
        return false;
    }
}

3.判断是否是数字:

bool IsDigit(char digit) //判断是否是数字
{
    if(digit >='0' && digit <='9')
    {
        return true;
    }
    else
    {
        return false;
    }
}

 

三、系统编码及运行

 (一)系统开发涉及软件:

  • 代码编写工具:CodeBlocks
  • 画图工具:Microsoft Visio
  • 文档撰写工具:Microsofr Word

(二)系统运行界面及结果:

编译原理——用C++模拟一个词法分析器_第5张图片

四、系统测试 

编译原理——用C++模拟一个词法分析器_第6张图片

 

编译原理——用C++模拟一个词法分析器_第7张图片

 

五、总结

该系统为简单的词法分析器,可以对输入的字符串进行分析,判断是关键字、标识符、运算符、界符和常数,然后输出。整个系统项目开发的过程中,提高了我对编译原理这门学科的实践能力,加深了我对其的理解,一学期的学习只是限于书本层次和实验层次,只有亲手自己动手动脑,制作出词法分析器,才从根本上理解词法分析的原理,这也让我深深地体会到学习,就应该是书本知识结合实践过程,实践出真知,才能真正将所学的知识融会贯通,把印在纸页上的文字变成自己脑中的能量。而且一定要深知一个大的程序是由许多小的程序组成的,总的程序就是对这些小的程序的调用,来实现各项功能。只要处理好这些小的程序,做出一个大的程序并不是很困难。而且要认真的把自己所学的知识结合起来,比如编译原理和C++,根据C++课程所学的概念、理论和方法,按照C++程序设计的基本步骤,设计出一个适当规模的程序;进一步加深对C++语言和编译原理的理解和掌握。理论联系实际,加深和巩固所学的理论知识,进而提高实践能力和计算机的综合运用能力

 

六、源代码

#include 
#include 
#include 
#include 
using namespace std;

/*
对所有可数符号进行编码:
<$,0>

...

<+,33>
<-,34>
<*,35>

<<,37>
<<=,38>
<>,39>
<>=,40>
<=,41>
<==,42>

<;,44>
<(,45>
<),46>
<^,47>
<,,48>
<",49>
<',50>
<#,51>
<&,52>
<&&,53>
<|,54>
<||,55>
<%,56>
<~,57>
<<<,58>左移
<>>,59>右移
<[,60>
<],61>
<{,62>
<},63>
<\,64>
<.,65>

<:,67>

"[","]","{","}"
<常数99  ,数值>
<标识符100 ,标识符指针>
*/

static char resercveWord[32][20]=
{
    "auto", "break", "case", "char", "const", "continue",
    "default", "do", "double", "else", "enum", "extern",
    "float", "for", "goto", "if", "int", "long",
    "register", "return", "short", "signed", "sizeof", "static",
    "struct", "switch", "typedef", "union", "unsigned", "void",
    "volatile", "while"
}; //保留字表

static char operatorOrDelimiter[36][10] =
{
    "+", "-", "*", "/", "<", "<=", ">", ">=", "=", "==",
    "!=", ";", "(", ")", "^", ",", "\"", "\'", "#", "&",
    "&&", "|", "||", "%", "~", "<<", ">>", "[", "]", "{",
    "}", "\\", ".", "\?", ":", "!"
}; // 运算符、界符表

static  char IDentifierTbl[1000][50] = { "" };//标识符表

int searchReserve(char reserveWord[][20],char s[]) //查找保留字
{
    for(int i=0; i<32; i++)
    {
        if(strcmp(reserveWord[i],s)==0)//如果查询成功
        {
            return i+1;
            //返回种别码
        }
    }
    return -1;//查找失败,返回-1,即标识符
}

bool IsLetter(char letter)//查找是否是字母
{
    if(letter >='a' && letter <='z' || letter >='A' && letter <='Z' || letter =='_')
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool IsDigit(char digit) //判断是否是数字
{
    if(digit >='0' && digit <='9')
    {
        return true;
    }
    else
    {
        return false;
    }
}

void filterResource(char r[],int pProject)  //编译预处理,取出无用的字符和注释
{
    char tempString [10000];
    int count =0;
    for(int i=0; i<=pProject; i++)
    {
        if(r[i]=='/' && r[i+1]=='/')  //识别注释。删除双杠之后的东西
        {
            while(r[i]!='\n')
            {
                i++;//向后扫描
            }
        }
        if(r[i]=='/' && r[i+1]=='*') //多行注释,删除其中的内容
        {
            i+=2;
            while(r[i]!='*' && r[i+1]!='/')
            {
                i++;
                if(r[i]=='$')
                {
                    printf("注释出错,没有找到*/,程序结束\n");
                    exit(0);
                }
            }
            i+=2; //跨过*/
        }
        if(r[i] != '\n' && r[i] != '\t' && r[i] != '\v' && r[i] != '\r')
        {
            //若出现无用字符,则过滤;否则加载
            tempString[count++] = r[i];
        }
        tempString [count]='\0'; //清零
        strcpy(r,tempString);
        //产生净化处理之后的源程序
    }
}

void Scanner(int &syn, char resourceProject[], char token[], int &pProject)
//分析子程序
//根据DFA的状态转换图设计
{
    int i;
    int count=0; // 用来做token[]指示器
    char ch;
    ch = resourceProject[pProject];
    while(ch==' ') // 过滤空格
    {
        pProject++;
        ch=resourceProject[pProject];
    }
    for(int i =0; i<20; i++)
    {
        //每次收集前先清零
        token[i]='\0';
    }
    if(IsLetter(resourceProject[pProject]))  //开头是字母
    {
        token[count ++] =resourceProject[pProject]; //收集
        pProject++;//move on
        while(IsLetter(resourceProject[pProject]) || IsDigit(resourceProject[pProject]))
            //后跟数字或者字母
        {
            token[count ++ ] =resourceProject[pProject];
            pProject++;
        }
        token[count]='\0';
        syn=searchReserve(resercveWord,token);
        if(syn==-1) //不是保留字而是标识符
        {
            syn=100;
            //标识符种别码
        }
        return;
    }
    else if(IsDigit(resourceProject[pProject]))
        //首字符是数字
    {
        while(IsDigit(resourceProject[pProject]))
            //后跟数字
        {
            token[count ++] = resourceProject[pProject];
            pProject++;
        }
        token[count] ='\0';
        syn=99;
        //常数种别码
    }
    else if(ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == ';' || ch == '(' || ch == ')' || ch == '^'
            || ch == ',' || ch == '\"' || ch == '\'' || ch == '~' || ch == '#' || ch == '%' || ch == '['
            || ch == ']' || ch == '{' || ch == '}' || ch == '\\' || ch == '.' || ch == '\?' || ch == ':')
        //若是运算符或者界符,查表得到结果
    {
        token[0]= resourceProject[pProject];
        token[1]='\0';
        for(int i =0; i<36; i++)
        {
            if(strcmp(token,operatorOrDelimiter[i])==0)
            {
                syn=33+i;
                //获得种别码
                break;
            }
        }
        pProject++;
        return ;
    }
    else if(resourceProject[pProject]=='<')
    {
        //<,<=,<<
        pProject++;//后移,超前搜索
        if(resourceProject[pProject] == '=')
        {
            syn = 38;
        }
        else if(resourceProject[pProject] == '<')
        {
            //左移
            pProject--;
            syn = 58;
        }
        else
        {
            pProject--;
            syn = 37;
        }
        pProject++;//指针下移
        return;
    }
    else if(resourceProject[pProject]=='>')
    {
        //>,>=,>>
        pProject++;
        if(resourceProject[pProject] == '=')
        {
            syn = 40;
        }
        else if(resourceProject[pProject] == '>')
        {
            syn = 59;
        }
        else
        {
            pProject--;
            syn = 39;
        }
        pProject++;
        return;
    }
    else if(resourceProject[pProject]=='=')
    {
        //=.==
        pProject++;
        if(resourceProject[pProject] == '=')
        {
            syn = 42;
        }
        else
        {
            pProject--;
            syn = 41;
        }
        pProject++;
        return;
    }
    else if(resourceProject[pProject]=='!')
    {
        //!,!=
        pProject++;
        if(resourceProject[pProject] == '=')
        {
            syn = 43;
        }
        else
        {
            syn = 68;
            pProject--;
        }
        pProject++;
        return;
    }
    else if(resourceProject[pProject]=='&')
    {
        //&,&&
        pProject++;
        if(resourceProject[pProject] == '&')
        {
            syn = 53;
        }
        else
        {
            pProject--;
            syn = 52;
        }
        pProject++;
        return;
    }
    else if(resourceProject[pProject]=='|')
    {
        //|,||
        pProject++;
        if(resourceProject[pProject] == '|')
        {
            syn = 55;
        }
        else
        {
            pProject--;
            syn = 54;
        }
        pProject++;
        return;
    }
    else if(resourceProject[pProject]=='$')
    {
        //结束符
        syn = 0;//种别码为0
    }
    else
    {
        //不能被以上词法分析识别,则出错。
        printf("error:there is no exist %c \n", ch);
        exit(0);
    }
}

int main()
{
    char resourceProject[10000];
    char token[20]= {0};
    int syn=-1,i;
    int pProject=0;
    FILE *fp;
    FILE *fp1;
    if((fp = fopen("D:\\DEAR_LESLIE_READ.txt ","r")) == NULL)
    {
        //打开源程序
        cout << "can't open this file";
        exit(0);
    }
    resourceProject[pProject]=fgetc(fp);//文件指针读取文件中的一个字符
    while(resourceProject[pProject]!='$')
    {
        pProject++;
        resourceProject[pProject]=fgetc(fp);
    }
    resourceProject[++pProject]='\0';//重置
    fclose(fp);
    cout<< endl << "源程序为:" << endl;
    cout << resourceProject << endl;
    filterResource(resourceProject,pProject);
    cout << endl << "过滤之后的程序:" << endl;
    cout << resourceProject << endl;
    pProject=0;
    //从头开始读取
    if((fp1= fopen("D:\\DEAR_LESLIE_COMPILE.txt ","w+"))==NULL)
    {
        cout << "can't open this file";
        exit(0);
    }
    while(syn!=0)
    {
        Scanner(syn,resourceProject,token,pProject);
        if(syn==100) //标识符
        {
            for(int i=0; i<1000; i++)
            {
                //插入标识符表中
                if(strcmp(IDentifierTbl[i],token)==0)
                {
                    break;
                }
                if(strcmp(IDentifierTbl[i],"")==0)
                {
                    strcpy(IDentifierTbl[i],token);
                    break;
                }
            }
            printf("(标识符,%s)\n",token);
            fprintf(fp1,"标识符,%s\n",token);
        }
        else if(syn >= 1 && syn <= 32)
        {
            //保留字
            printf("(%s,--)\n", resercveWord[syn - 1]);
            fprintf(fp1, "(%s,--)\n", resercveWord[syn - 1]);
        }
        else if(syn == 99)
        {
            //const 常数
            printf("(常数,%s)\n", token);
            fprintf(fp1, "(常数,%s)\n", token);
        }
        else if(syn>=33 && syn<=68)
        {
            printf("(%s,--)\n", operatorOrDelimiter[syn - 33]);
            fprintf(fp1, "(%s,--)\n", operatorOrDelimiter[syn - 33]);
        }
    }
    for(int i=0; i<100; i++)
    {
        //插入标识符表中
        printf("第&d个标识符:%s\n",i+1,IDentifierTbl[i]);
        fprintf(fp1, "第%d个标识符:%s\n", i + 1, IDentifierTbl[i]);
    }
    fclose(fp1);
    return 0;
}

 

 

你可能感兴趣的:(编译原理,词法分析器,编译原理)