编译原理_PL/0_词法分析

来源:互联网 发布:单片机液晶数字钟论文 编辑:程序博客网 时间:2024/05/18 20:10

词法分析器

一、实验要求:

PL0语言设计一个词法分析器,能够识别PL0语言编写的程序中的单词的合法性。

二、实验分析:

PL0语言是与pascal类似的语言,但是比pascal语言的词法、语法更加严格苛刻。

PL0语言中,只有五种类型的单词:关键字、用户自定义标识符、数字、特殊符号以及非法标识符。

根据题目给出的PL0的文法,关键字只有如下几个:

Const——用来声明常量;

Var——用来声明变量;

Procedure——用来声明过程;

Begin——声明过程的开始;

End——声明过程的结束;

Ood——单目运算符,声明为关键字;

If——条件语句;

Then——条件语句;

Call——声明调用函数;

While——循环语句;

Read——读文件语句;

Write——写文件语句;

对于用户自定义标识符应该满足:以字母开头,其后是字母和数字的组合,即:字母(字母|数字)*

数字则是数字字符(0——9)组成的数字串;

特殊符号有如下几个:
+-*/=<>,<,>=,<=,:=

把关键字、算符和界符称为语言固有的单词,标识符、常量称为用户自定义的单词。

为此设置三个全程量:SYM,ID,NUM

SYM:存放每个单词的类别,为内部编码的表示形式;

ID:存放用户所定义的标识符的值,即标识符字符串的机内表示;

NUM:存放用户定义的数。

三、设计方案:
   1)对于关键字,应该为每个关键字分配一个类别,该类别在机器内部为内部编码的表示形式;

    采用枚举型,将所有的关键字放在一个表中,这样每一个关键字都分配到了一个内部编码;

   2)对于用户自定义的标识符,应该给他们一个统一的类别——SYM_IDENTIFIER

   3)对于所有的特殊符号(例如+-等),与关键字的处理方法一样给每一个符号分配一个类别。

   4)上述所有的类别均放在一个枚举类型表中。

   5)对于关键字,还应该有一个字符串类型的表,用来存储每一个关键字的名称。当词法分析器读入一个单词的时候,首先判断是不是关键字时,立马转入到表中,和表中的关键字进行匹配,如果匹配成功,则读入的标识符时关键字,否则进行进一步的判断;

四、程序设计:

   PL/0语言建立一个词法分析程序GETSYM(函数)

   GETSYM要完成的任务:

1、滤掉单词间的空格;

2、识别关键字,用查关键字表的方法识别。当单词是关键字时,将对应的类别放在SYM中;

3、识别标识符,标识符的类别为SYM_IDENTIFIERSYM_IDENTIFIER放在SYM中,标识符本身的值放在ID中。关键字或者标识符的最大长度是10

4、拼数,将数的类别NUMBER放在SYM中,数本身的值放在NUM中;

5、拼由两个字符组成的运算符,如:>=,<=等等,识别后将类别放在SYM中;

6、错误处理,如果读入不能识别的单词、或者非法的字符、或者长度超过10的字符,就会报错。定义一个全局变量line记录当前读到程序的行数,报错时指示错误的行数;

7、打印源程序,边都入字符表打印;

8、为GETSYM设计一个GETCH函数,该函数读入一个字符;

9、定义一个判断是不是关键字的函数。

程序流程图:

五、代码实现:

C语言开发,首先定义mycode.h文件,定义数据结构和用来查询和显示的数组:

const int KEY_NUM = 13;//关键字个数

const int OP_NUM = 13;//操作符个数

 

enum SymType

{

       //keywords

       SYM_BEGIN,//begin

       SYM_CALL,//call

       SYM_CONST,//const

       SYM_DO,//do

       SYM_END,//end

       SYM_IF,//if

       SYM_OOD,//ood

       SYM_PROCEDURE,//procedure

       SYM_THEN,//then

       SYM_VAR,//var

       SYM_WHILE,//while

       SYM_READ,//read

       SYM_WRITE,//write

 

       SYM_IDENTIFIER,//标识符

 

       SYM_NUMBER,//数字

       //特殊符号

       SYM_PLUS,//+

       SYM_MINUS,//-

       SYM_TIMES,//*

       SYM_SLASH,///

       SYM_EQU,//=

       SYM_NEQ,//#

       SYM_LES,//<

       SYM_LEQ,//<=

       SYM_GTR,//>

       SYM_GEQ,//>=

       SYM_LPAREN,//(

       SYM_RPAREN,//)

       SYM_COMMA,//,

       SYM_SEMICOLON,//;

       SYM_PERIOD,//.

       SYM_BECOMES,//:=

 

};

int key_table[KEY_NUM] = {   SYM_BEGIN, SYM_CALL, SYM_CONST, SYM_DO,SYM_END,

                                        SYM_IF, SYM_OOD, SYM_PROCEDURE, SYM_THEN,SYM_VAR, SYM_WHILE,

                                       SYM_READ,SYM_WRITE };

 

char *SYM_NAME[] = { "SYM_BEGIN","SYM_CALL", "SYM_CONST", "SYM_DO","SYM_END",

"SYM_IF", "SYM_OOD","SYM_PROCEDURE", "SYM_THEN", "SYM_VAR","SYM_WHILE",

"SYM_READ","SYM_WRITE",    "SYM_IDENTIFIER","SYM_NUMBER","SYM_PLUS","SYM_MINUS",

"SYM_TIMES","SYM_SLASH","SYM_EQU","SYM_NEQ","SYM_LES","SYM_LEQ","SYM_GTR",

"SYM_GEQ","SYM_LPAREN","SYM_RPAREN","SYM_COMMA","SYM_SEMICOLON","SYM_PERIOD",

"SYM_BECOMES",

};

 

char* key[KEY_NUM ] =

{

       "begin","call", "const", "do","end","if",

       "ood","procedure", "then", "var","while","read","write"

};

其中,数组char *SYM_NAME[]单纯只是为了显示才定义的量,对于机器内部的语法分析和词法分析没有丝毫作用。

函数GETCH():

int getch() {

       if((ch = fgetc(infile)) == EOF) {

              printf("文件内容已经读完!");

              return0;

       }

       else{

              return1;

       }

}

每次读入一个字符,一直读到文件的结尾。

函数GetSym()

int getSYM() {//每次得到一个单词,然后对这一个单词进行分析

       inti;

       //每次读入一个单词到token之前,必须将token清空

       chartoken[MAXLENGTH] = { '\0' };

       for(i = 0;i<MAXLENGTH;i++) {

              id[i]= '\0';

       }

       NUM= -1;

 

       getch();

       while(ch == ' ' || ch == '\n' || ch == '\t') {//滤掉一个单词之前的空格,记录行号

              if(ch=='\n')

               line++;

              getch();

       }

       if(ch == EOF) return 0;//如果文件内容已经读完,则返回0       

       if(isalpha(ch)) {//如果是字母,有可能是标识符或关键字  先判断是不是关键字

              intstart = 0;

              do{

                     token[start++]= ch;

                     getch();

              }while (isalpha(ch) || isdigit(ch));

              //printf("%s\n",ch);

              if(start>10) Error(2); //标识符或关键字最大长度为10

              strcpy(id,token);

              //printf("%s\n",id);

              fseek(infile,-1, 1);//将当前光标位置左移一位

              inttt = IsKey(id);//判断是不是关键字

              if(tt != -1) {

                     SYM= tt;

              }

              else{

                     SYM= SYM_IDENTIFIER;

              }

              return1;

       }

       elseif (isdigit(ch)) {

              intn = 0;

              do{

                     n= n * 10 + (ch - '0');

                     //printf("%d",n);

                     getch();

              }while (isdigit(ch));

              SYM= SYM_NUMBER;

              NUM= n;

              fseek(infile,-1, 1);//将当前光标位置左移一位

              return2;

       }

       elseif (ch == ':') {

              getch();

              if(ch == '=') {

                     SYM= SYM_BECOMES;

                     id[0]= ':';

                     id[1]= '=';

              }

              else{

                     Error(1);

              }

       }

       elseif (ch == '=') {

              SYM= SYM_EQU;

              id[0]= '=';

       }

       elseif (ch == '<') {

              getch();

              if(ch != '=') {

                     fseek(infile,-1, 1);//将当前光标位置左移一位

                     SYM= SYM_LES;

                     id[0]= '<';

              }

              else{

                     SYM= SYM_LEQ;

                     id[0]= '<';

                     id[1]= '=';

              }

       }

       elseif (ch == '>') {

              getch();

              if(ch != '=') {

                     fseek(infile,-1, 1);//将当前光标位置左移一位

                     SYM= SYM_GTR;

                     id[0]= '>';

              }

              else{

                     SYM= SYM_GEQ;

                     id[0]= '>';

                     id[1]= '=';

              }

       }

       elseif (ch == '+') {

              SYM= SYM_PLUS;

              id[0]= '+';

       }

       elseif (ch == '-') {

              SYM= SYM_MINUS;

              id[0]= '-';

       }

       elseif (ch == '*') {

              SYM= SYM_TIMES;

              id[0]= '*';

       }

       elseif (ch == '/') {

              SYM= SYM_SLASH;

              id[0]= '/';

       }

       elseif (ch == ';') {

              SYM= SYM_SEMICOLON;

              id[0]= ';';

       }

       elseif (ch == ',') {

              SYM= SYM_COMMA;

              id[0]= ',';

       }

       elseif (ch == '.') {

              SYM= SYM_PERIOD;

              id[0]= '.';

       }

       elseif (ch == '(') {

              SYM= SYM_LPAREN;

              id[0]= '(';

       }

       elseif (ch == ')')

       {

              SYM= SYM_RPAREN;

              id[0]= ')';

       }

       elseif (ch == '#')

       {

              SYM= SYM_NEQ;

              id[0]= '#';

       }

       else{

              Error(0);

       }

       return3;

}

读入一个单词,如果首字符是字母,先判断是不是关键字,调用IsKey()函数,函数内容如下:

int IsKey(char token[]) {

       inti;

       for(i = 0;i<KEY_NUM;i++) {

              if(strcmp(token, key[i]) == 0) {

                     returni;

              }

       }

       return-1;

}

如果不是关键字,则是标识符(长度<=10.

如果首字符是数字,则一直读取,直到遇到非数字字符为止。

在读入的过程中,采用do——while循环,这样可以向前读一个字符,从而预先判断是不是将要终止读入。

注意:在读入<、>的时候,要再往后读一个字符,判断此时应该读入的字符究竟是<=还是<。

错误处理函数接收一个错误编号,然后打印错误的内容和出现错误的位置等信息:

void Error(int ErrorID) {

       switch(ErrorID) {

       case0:

              printf("Error0:not found %c in line %d\n", ch,line);

              break;

       case1:

              printf("Error1:after colon must be = in line %d\n",line);

              break;

       case2:

              printf("Error2:the length of keyword or indentiy is above 10 in line %d,pleasecorrect\n",line);

              break;

       }

}

主函数则负责处理读入源文件、写入中间文件和打印、调用GetSym的工作。

int main(int argc, char *argv[]) {

 

       charfileName[MAXLENGTH];

       infile= fopen("PL0.txt", "r");

       outfile= fopen("PL0_lexical_analyzer.txt", "w");

       if(infile == NULL) {

              printf("该文件不存在。\n");

              getchar();

              exit(0);

       }

       intSYM_TYPE;

       while(SYM_TYPE = getSYM()) {

              switch(SYM_TYPE) {

              case1://关键字和标识符

                     printf("<%s    ,      %s>\n",SYM_NAME[SYM], id);

                     fprintf(outfile,"%s",SYM_NAME[SYM]);

                     fprintf(outfile,"  ");

                     fprintf(outfile,"%d", SYM);

                     fprintf(outfile,"  ");

                     fprintf(outfile,"%s", id);

                     fprintf(outfile,"%c", '\n');

                     break;

              case2://数字

                     printf("<%s    ,      %d>\n",SYM_NAME[SYM], NUM);

                     fprintf(outfile,"%s", SYM_NAME[SYM]);

                     fprintf(outfile,"  ");

                     fprintf(outfile,"%d", SYM);

                     fprintf(outfile,"  ");

                     fprintf(outfile,"%d", NUM);

                     fprintf(outfile,"%c", '\n');

                     break;

              case3://运算符,界符

                     printf("<%s    ,      %s>\n",SYM_NAME[SYM], id);

                     fprintf(outfile,"%s", SYM_NAME[SYM]);

                     fprintf(outfile,"  ");

                     fprintf(outfile,"%d", SYM);

                     fprintf(outfile,"  ");

                     fprintf(outfile,"%s", id);

                     fprintf(outfile,"  ");

                     fprintf(outfile,"%c", '\n');

                     break;

              }

       }

 

       fclose(infile);

       fclose(outfile);

       return0;

}

六、总结反思:

   词法分析器的主要功能在于过滤掉源程序中的空格、换行、制表符,判断每个单词的合法性。如果一个单词合法,则要返回它的类别和值,从而为语法分析服务;否则,停止分析并报错。

   在设计词法分析器的时候,最难的问题在于分析PL/0文法,找出它的关键字和特殊符号,并给出一个合适的数据结构用来进行查找匹配。一般的结构体和数组都不是很好的选择,但是枚举型却能够很好的解决这个问题。

 

七、源代码:

0 0
原创粉丝点击