算术表达式的自上而下语法分析及其实现(下)

(接中篇)

5. 自上而下语法分析程序的实现

经过上面4步精心的准备,最令人激动的时刻到了。一般《编译原理》课本上的代码大都是无法在机器上运行的伪代码,在这里,你将要看到的是一个实用的可以检查错误的可以执行求值的基于自上而下语法分析算法的计算算术表达式的程序。

不失一般性,我们规定算术表达式只可以进行整数的四则运算(含括号),这样我们需要扩充下面3个函数:


int E_AddSub();        //对应于非终结符E的产生式
int T_MulDiv();        //对应于非终结符T的产生式
int F_Number();        //对应于非终结符F的产生式


大家看到,上面的函数有返回值int,我们需要让这3个函数返回计算出的结果的值。为了计算出每个函数中子表达式的值,在E_AddSub()和T_MulDiv()函数中,我用一个变量rtn来存储运算符左部的值,用opr2来存储运算符右部的值,根据运算符进行相应的运算。

为了保存输入的算术表达式,我用全局静态字符数组expr来表示输入字符缓冲区,用pos来表示字符指示器的值,这样,指示器取下一个字符的advance()操作可以用pos++来代替,而指示器所指示的字符可以就是expr[pos]。

为了表示错误,我用宏定义了6种错误的错误代码,而且定义了对应的6条错误信息的字符串。同时把error()函数改造为:

void Error(int ErrCode);


这样通过传递错误代码可以使程序对错误进行相应的反应,包括指示错误位置、显示错误信息、发出提示音等。此外,我声明了出错跳转缓冲区静态变量errjb,errjb是一个std::jmp_buf类型的结构,可以通过setjmp()宏把当前程序的运行状态记录到errjb中,错误返回时,可以通过longjmp()函数;直接跳转到main()主程序setjmp()被调用的位置,而不是出错的函数体中。

这样,一个功能齐全的算术表达式分析执行器构造完毕,注意,这样构造的程序不能识别一元运算符,比如输入“-1+1”会报错。

下面是运行结果片段:

1+(
   ^ 语法错误 !!!  表达式非法结束或表达式不完整!
请重新输入!
请输入一个算术表达式(输入“Q”或“q”退出):
2-()
   ^ 语法错误 !!!  括号内无表达式或表达式不完整!
请重新输入!
请输入一个算术表达式(输入“Q”或“q”退出):
2+(3+
     ^ 语法错误 !!!  表达式非法结束或表达式不完整!
请重新输入!
请输入一个算术表达式(输入“Q”或“q”退出):
2+(3*9)+
        ^ 语法错误 !!!  表达式非法结束或表达式不完整!
请重新输入!
请输入一个算术表达式(输入“Q”或“q”退出):
2*(2+4)4
       ^ 语法错误 !!!  右括号后连接非法字符!
请重新输入!


程序清单如下:

/****算术表达式的分析和计算,文件名:Exp_c.cpp,代码/注释:hifrog****
*****                  在VC6和Dev-C下调试通过                ****/
#include
#include
#include
#include
#include
   
#define EXP_LEN    100                       //定义输入字符缓冲区的长度

/*------------出错代码的宏定义--------------*/
#define INVALID_CHAR_TAIL 0            //表达式后跟有非法字符
#define CHAR_AFTER_RIGHT 1           //右括号后连接非法字符
#define LEFT_AFTER_NUM  2             //数字后非法直接连接左括号
#define INVALID_CHAR_IN  3              //表达式中含有非法字符
#define NO_RIGHT   4                         //缺少右括号
#define EMPTY_BRACKET  5              //括号内无表达式
#define UNEXPECTED_END  6            //预期外的算术表达式结束

using namespace std;

const string ErrCodeStr[]=                   //表达式出错信息
{
 "表达式后跟有非法字符!",
 "右括号后连接非法字符!",
 "数字后非法直接连接左括号!",
 "表达式中含有非法字符!",
 "缺少右括号!",
 "括号内无表达式或表达式不完整!",
 "表达式非法结束或表达式不完整!"
};

static char expr[EXP_LEN];                 //算术表达式输入字符缓冲区
static int pos;                                     //字符指示器标志:用来保存正在分析的字符的位置
static jmp_buf errjb;                            //出错跳转缓冲器

//********以下是函数声明*********
//产生式“E -> T+E | T-E | T”的函数,用来分析加减算术表达式。
int E_AddSub(); 
//产生式“T -> F*T | F/T | F”的函数,用来分析乘除算术表达式。
int T_MulDiv(); 
//产生式“F -> i | (E)”的函数,用来分析数字和括号内的表达式。
int F_Number();
//出错处理函数,可以指出错误位置,出错信息。
void Error(int ErrCode);

int main()
{
 int ans;                                          //保存算术表达式的计算结果
 bool quit=false;                               //是否退出计算

 do
 {
  //在此设定一个跳转目标,如果本程序的其他函数调用longjmp,
  //执行指令就跳转到这里,从这里继续执行。
  if(setjmp(errjb)==0)                       //如果没有错误
  {
   pos=0;               //初始化字符指示器为0,即指向输入字符串的第一个字符。

   cout<<"请输入一个算术表达式(输入“Q”或“q”退出):"<
   cin>>expr;                                //输入表达式,填充表达式字符缓冲区。

   if(expr[0]=='q'||expr[0]=='Q')
                                                  //检查第一个字符,是否退出?
    quit=true;

   else
   {
    //调用推导式“E -> T+E | T-E | T”的函数,
    //从起始符号“E”开始推导。
    ans=E_AddSub();

    //此时,程序认为对表达式的语法分析已经完毕,下面判断出错的原因:

    //如果表达式中的某个右括号后直接跟着数字或其他字符,
    //则报错,因为数字i不属于FOLLOW())集。
    if(expr[pos-1]==')'&&expr[pos]!='/0')
     Error(CHAR_AFTER_RIGHT);

    //如果表达式中的某个数字或右括号后直接跟着左括号,
    //则报错,因为左括号不属于FOLLOW(E)集。
    if(expr[pos]=='(')
     Error(LEFT_AFTER_NUM);

    //如果结尾有其他非法字符
    if(expr[pos]!='/0')
     Error(INVALID_CHAR_TAIL);
    
    cout<<"计算得出表达式的值为:"< <
   }
  }
  else
  {
   //setjmp(errjb)!=0的情况:
   cout<<"请重新输入!"<
  }
 }
 while(!quit);

 return 0;
}

//产生式“E -> T+E | T-E | T”的函数,用来分析加减算术表达式。
//返回计算结果
int E_AddSub()
{
 int rtn=T_MulDiv();              //计算加减算术表达式的左元

 while(expr[pos]=='+'||expr[pos]=='-')
 {
  int op=expr[pos++];          //取字符缓冲区中当前位置的符号到op
  int opr2=T_MulDiv();          //计算加减算术表达式的右元

  //计算求值
  if(op=='+')                        //如果是"+"号
   rtn+=opr2;                      //则用加法计算
  else                                //否则(是"-"号)
   rtn-=opr2;                       //用减法计算
 }
 return rtn;
}

//推导式“T -> F*T | F/T | F”的函数,用来分析乘除算术表达式。
//返回计算结果
int T_MulDiv()
{
 int rtn=F_Number();          //计算乘除算术表达式的左元

 while(expr[pos]=='*'||expr[pos]=='/')
 {
  int op=expr[pos++];        //取字符缓冲区中当前位置的符号到op
  int opr2=F_Number();      //计算乘除算术表达式的右元

  //计算求值
  if(op=='*')                       //如果是"*"号
   rtn*=opr2;                     //则用乘法计算
  else                              //否则(是"/"号)
   rtn/=opr2;                     //用除法计算
 }
 return rtn;
}

//产生式“F -> i | (E)”的函数,用来分析数字和括号内的表达式。
int F_Number()
{
 int rtn;                            //声明存储返回值的变量

 //用产生式F->(E)推导:
 if(expr[pos]=='(')              //如果字符缓冲区当前位置的符号是"("
 {
  pos++;                         //则指示器加一指向下一个符号
  rtn=E_AddSub();           //调用产生式“E -> T+E | T-E | T”的分析函数
  
  if(expr[pos++]!=')')         //如果没有与"("匹配的")"
   Error(NO_RIGHT);        //则产生错误

  return rtn;
 } 


 if(isdigit(expr[pos]))        //如果字符缓冲区中当前位置的字符为数字
 {
  //则用产生式F -> i推导
  //把字符缓冲区中当前位置的字符串转换为整数
  rtn=atoi(expr+pos);
  //改变指示器的值,跳过字符缓冲区的数字部分,找到下一个输入字符。
  while(isdigit(expr[pos]))
   pos++;
 }
 else                                      //如果不是数字则产生相应的错误
 {
  if(expr[pos]==')')                    //如果发现一个")"
   Error(EMPTY_BRACKET);    //则是括号是空的,即括号内无算术表达式。
  else if(expr[pos]=='/0')           //如果此时输入串结束
   Error(UNEXPECTED_END);  //则算术表达式非法结束
  else
   Error(INVALID_CHAR_IN);    //否则输入字符串中含有非法字符
 }

 return rtn;
}

//出错处理函数,输入错误代码,可以指出错误位置,出错信息。
void Error(int ErrCode)
{
 cout<<'/r';                       //换行
 while(pos--)
  cout<<' ';                      //打印空格,把指示错误的"^"移到输入字符串的出错位置
 cout<<"^ 语法错误 !!!  "
  <   < <<'/A';??????????????? FONT 发出警报音<>

 longjmp(errjb,1);            //跳转到main()函数中的setjmp调用处,并设置setjmp(errjb)的返回值为1
}


(全文完)

  • 1
    点赞
  • 4
    收藏
  • 打赏
    打赏
  • 12
    评论
毕业论文引言 随着计算机技术的发展与普及,计算机已经成为各行业最基本的工具之一,迅速进入千家万户。因此,掌握计算机应用的基本技能成为新世纪人才不可缺少的基本素质之一。为使计算机能正常工作, 除了构成计算机各个组成部分的物理设备外, 一般说来, 还必须要有指挥计算机“做什么”和“如何做”的“程序”。程序及其有关文档构成计算机软件, 其中用以书写计算机软件的语言称为计算机程序设计语言。 1 计算机程序设计语言简介 计算机程序设计语言是计算机可以识别的语言,用于描述解决问题的方法,供计算机阅读和执行,通常简称为编程语言,是一组用来定义计算机程序的语法规则。它是一种被标准化的交流技巧,用来向计算机发出指令。一种计算机语言让程序员能够准确地定义计算机所需要使用的数据,并精确地定义在不同情况下所应当采取的行动。使用程序设计语言往往使程序员能够比使用机器语言更准确地表达他们所想表达的目的。对那些从事计算机科学的人来说,懂得程序设计语言是十分重要的,因为所有的程序都需要程序设计语言才能完成,而计算机的工作是用程序来控制的,离开了程序,计算机将一事无成。 2 开发背景及意义 现有计算器不能计算表达式,这是一个缺陷,为此,开发了一个能直接计算表达式的计算器,这为计算提高了更大的方便,可以大幅度提高计算效率。 第二章 第三章 第一节 递归下降法的描述 3.1.1实现思想 它的主要原理是,对每个非终极符按其产生式结构构造相应语法分析子程序,其中终极符产生匹配命令,而非终极符则产生过程调用命令。因为文法递归相应子程序也递归,所以称这种方法为递归子程序下降法或递归下降法。其中子程序的结构与产生式结构几乎是一致的。文法中每个非终结符对应一个递归过程(子程序),每个过程的功能是识别由该非终结符推出的串,当某非终结符的产生式有多个候选式时能够按LL(1)形式可唯一地确定选择某个候选式进行推导。 3.1.2算法的特点 递归下降法是语法分析中最易懂的一种方法。递归下降法要满足的条件:假设A的全部产生式为Aα1|α2|……|αn ,则必须满足如下条件才能保证可以唯一的选择合适的产生式 predict(Aαi)∩predict(Aαj)=Φ,当i≠j. 3.1.3构造递归下降语法分析程序 采用了递归子程序方法进行语法分析,对文法中的每个非终极符号按其产生式结构产生相应的语法分析子程序,完成相应的识别任务。其中终结符产生匹配命令,非终结符则产生调用命令。每次进入子程序之前都预先读入一个单词。因为使用了递归下降方法,所以程序结构和层次清晰明了,易于手工实现,且时空效率较高。实际的语法分析工作,从调用总程序的分析子程序开始,根据产生式进行递归调用各个分析子程序。 第二节

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 12

打赏作者

hifrog

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值