返回介绍

第 1 节 c 语言词法分析器

发布于 2025-03-07 00:37:22 字数 10677 浏览 0 评论 0 收藏 0

一、项目说明

1. 项目使用

本实验环境采用带桌面的 Ubuntu Linux 环境,实验中会用到桌面上的程序:

  • LX 终端(LXTerminal): Linux 命令行终端,打开后会进入 Bash 环境,可以使用 Linux 命令
  • GVim:非常好用的编辑器,最简单的用法可以参考课程 Vim 编辑器

2. 环境使用

使用 GVim 编辑器输入实验所需的代码及文件,使用 LX 终端(LXTerminal)运行所需命令进行操作。

实验报告可以在个人主页中查看,其中含有每次实验的截图及笔记,以及每次实验的有效学习时间(指的是在实验桌面内操作的时间,如果没有操作,系统会记录为发呆时间)。这些都是您学习的真实性证明。

4. 项目简介

项目目的:设计并实现一个包含预处理功能的词法分析程序,加深对编译中词法分析过程的理解。

项目要求:

1、实现预处理功能 源程序中可能包含有对程序执行无意义的符号,要求将其剔除。 首先编制一个源程序的输入过程,从键盘、文件或文本框输入若干行语句,依次存入输入缓冲区(字符型数据);然后编制一个预处理子程序,去掉输入串中的回车符、换行符和跳格符等编辑性文字;把多个空白符合并为一个;去掉注释。

2、实现词法分析功能 输入:所给文法的源程序字符串。 输出:二元组构成的序列。 具体实现时,可以将单词的二元组用结构进行处理。

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 default sizeof do
所有的关键字都是小写。

2)运算符和界符 “ + - * / : := < <> <= > >= = ; ( ) #”

3)其他标记 ID 和 NUM 通过以下正规式定义其他标记: ID→letter(letter|digit)* NUM→digit digit* letter→a|…|z|A|…|Z digit→0|…|9…

4)空格由空白、制表符和换行符组成 空格一般用来分隔 ID、NUM、专用符号和关键字,词法分析阶段通常被忽略。

4、各种单词符号对应的种别码

图片描述信息

功能流程图(代码实现思路基本根据流程图来的):

图片描述信息

二、项目实现

实践出真知,只有在实践过程中才能发现不足,现在让我们打开命令行,键入命令新建一个 .cpp 的文本:

图片描述信息

从流程图可以知道,我们需要判断字符是否为数字、字母、定界符、关键字 我们通过 IsDigit()、IsLetter()、IsSymbol()、IsKeyword() 去实现这个四个功能

第一个函数 IsDigit()

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

第二个函数 IsLetter()

//判断是否为字母 
bool IsLetter(char ch)
{
  if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z'))
    return true;
  return false;
}

第三个函数 IsSymbol()

//判断是否为定界符等
int IsSymbol(char ch)
{
  for(int i=0;  i<9; i++)
  {
    if(ch==symbol[i])
      return i; 
  }
  return -1;
} 

第四个函数 IsKeyword()

//判断是否为关键字 
int IsKeyword(string str)
{
  for(int i=0;  i<26;  i++)
  {
    if(str==keyword[i])
    {
      return i;
    }
  }
  return 25;
}

读者仔细阅读上面项目要求会发现,还有一个预处理的要求,需要合并空格,去掉注释的功能,下面我们就来完成合并空格的功能。

//空格处理
void HandleSpace(char a[])
{
    int j=0;
    memset(word,0,255);
    temp=false;
    for(int i=0;  i<strlen(a); i++)
    {

      if(a[i]!=' ' && a[i]!='\t')  //'\t'是 table 键
        {  
          word[j++]=a[i];
          temp=false;
        }
      else
      {

        if(!temp&&a[i]!='\t')
        {
          word[j++]=a[i];
          temp=true;
        }

      }
    } 
} 

然后是处理注释,这里我是将 // 注释进行了预处理, /* */ 注释是在主程序中处理的>

//处理"//"注释
void prePro()
{
  int j=0;
  memset(tempstr,0,255);
  for(int i=0;   i<strlen(word); i++)
  {
    if(word[i]=='/'&&word[i+1]=='/')
    {
      while(i<strlen(word))
      {
        i++;
      }
    }

    else {
        tempstr[j++]=word[i];

    }
  }

}

这样整个程序的核心大部分就完成了,思路就是判断读入的第一个单词是否为字母,若为字母,则为关键字或者标识符,若为数字则为 NUM。

三、完整源码

整个程序的源代码如下:

/*
*author:leetao
*contact:leetao94cn@gmail.com
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;

//存放处理后的字符串 
char tempstr[255]={};
//空格标志 
bool temp=false;
//临时数组
char word[255]={}; 
//keyword 关键字 
string keyword[26]={
"main","if","then","while","do","static","defualt","do","int","double","struct","break","else","long","swtich","case","typedf","char","return","const","float","short","continue","for","void","sizeof"};

int keyword_num[26]={1,2,3,4,5,6,39,40,7,8,9,10,11,
      12,13,14,15,16,17,18,19,20,21,22,23,24};
//部分运算符,定界符等 
char symbol[9]={'+','-','*','/','=',';','(',')','#'};
//对应的种码值
int symbol_num[9]={27,28,29,30,38,41,42,43,0};

//判断是否为字母 
bool IsLetter(char ch)
{
  if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z'))
    return true;
  return false;
}

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

//判断是否为定界符等
int IsSymbol(char ch)
{
  for(int i=0;  i<9; i++)
  {
    if(ch==symbol[i])
      return i; 
  }
  return -1;
} 

//判断是否为关键字 
int IsKeyword(string str)
{
  for(int i=0;  i<26;  i++)
  {
    if(str==keyword[i])
    {
      return i;
    }
  }
  //不是关键字即为 ID
  return 25;
}

//空格处理
void HandleSpace(char a[])
{
    int j=0;
    memset(word,0,255);//需要清空,不然可能残留上次的字符串
    temp=false;
    for(int i=0;  i<strlen(a); i++)
    {

      if(a[i]!=' ' && a[i]!='\t') 
        {  
          word[j++]=a[i];
          temp=false;
        }
      else
      {

        if(!temp&&a[i]!='\t')
        {
          word[j++]=a[i];
          temp=true;
        }

      }
    } 
} 

//处理"//"注释
void prePro()
{
  int j=0;
  memset(tempstr,0,255);
  for(int i=0;   i<strlen(word); i++)
  {
    if(word[i]=='/'&&word[i+1]=='/')
    {
      while(i<strlen(word))
      {
        i++;
      }
    }

    else {
        tempstr[j++]=word[i];
       }
  }
}

int main()
{  
  char instr[255]={}; //接收输入字符串 
  bool flag=false; //多行注释标志,false 为未处于注释区域 
  string Token;//存放字符串 
  char *str=NULL;//存放每行的字符串 
  char delims[]=" ";//分割标志 
  freopen("test.cpp","r",stdin);
  freopen("result.txt","w",stdout); //此行注释后,控制台输出,
  //否则文本输出
  while((gets(instr))!=NULL)
  {
    HandleSpace(instr); 
    prePro();

     str=strtok(tempstr,delims);//分割字符串 

     while(str!=NULL) 
      {
          //头文件,宏定义
        if(*(str)=='#') 
        {
          printf("#\n"); 
          break;
        }

        for(int i=0;  i<strlen(str);i++)
        {
          if(*(str+i)=='/')
            {
              if(*(str+i+1)=='*')
              {
                flag=true;
                break;
              }
            } 
            //注释处理: */,注释区域结束 
            if(*(str+i)=='*'&&flag)
            {
              if(*(str+i+1)=='/')
              {
                flag=false;
                i++;
                break;
              }
            }
            //标识符,关键词 
            if(IsLetter(*(str+i))&&(!flag))
            {
//          printf("进入标识符判断\n");
    while(IsLetter(*(str+i))||IsDigit(*(str+i))
    ||*(str+i)=='_')
              {
                Token+=*(str+i);
                i++;
              }

    if(IsKeyword(Token)!=25) 
            {
        printf("%s---->%d\n",Token.c_str(),
            keyword_num[IsKeyword(Token)]);
              }
        else printf("%s---->25\n",Token.c_str());

            Token="";
//        printf("退出标识符判断\n");
            }     
          if(IsDigit(*(str+i))&&(!flag))
            {
//            printf("进入数字判断\n");
              while(IsDigit(*(str+i)))
                {
                  Token+=*(str+i);
                  i++;
                }
        printf("%s------>26\n",Token.c_str());
              Token="";
            }

          //<,<=,<>
        if(*(str+i)=='<'&&(!flag))
            {
              if(*(str+i)=='=')   {printf("<=------>35\n");i++;} 
              if(*(str+i)=='>')  {printf("<>------>34\n");i++;}
              else printf("<------>33\n");
            }
          //>,>=
            else if(*(str+i)=='>'&&(!flag))
            {
              if(*(str+i+1)=='=') {printf(">------>37\n");}
              else printf(">-------36\n");
            }
          //:,:=
            else  if(*(str+i)==':'&&(!flag))
            {
              if(*(str+i+1)=='=') {printf(":=------->32\n");}
              else printf(":-------->31\n");
            }
          //余下定界符等
            else if(IsSymbol(*(str+i))!=-1&&(!flag))
            {
              printf("%c------->%d\n",*(str+i),
                  symbol_num[IsSymbol(*(str+i))]);
            } 
          } 
        str=strtok(NULL,delims);
        }
    }

  return 0;
}

这个代码完成了我们还需要一个程序,在当前目录下使用命令行:gvim test.cpp 新建一个测试程序 test.cpp 文件代码如下(读者也可以自行发挥):

#include<stdio.h>
int main()
{
  //test

  /* test */
    for(int i=1;i<0;i++)
    printf("%d",i);

    return 0;
}

图片描述信息

四、编译运行

自此准备工作都完成了,现在开始编译了:

g++ testword.cpp -o testword

图片描述信息

注意是使用 g++ 而不是 gcc 编译,会出现 waring,不用管,gets() 函数在输入时没有限定字符串的长度,而 linux 是很严谨的,所以这里给出一 warning。 这个时候输入 ls,发现目录下已经出现编译成功的 testword 可运行程序,然后运行,成功运行结果如图:

图片描述信息

有个 result.txt 的文件,打开它,内容如下:

图片描述信息

本课程到此结束,谢谢学习,如有问题请留言,我会定期回复。

五、作业思考

实验楼环境中暂时无法输入中文字符,但是在实际生活应用中,中文是很常见的。考虑一下,如果遇到中文字符,词法分析器该怎么解决。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文