表达式解析器

本文介绍了一个支持加减乘除、逻辑运算等四则混合运算的表达式解析器的实现。解析器采用递归拆解表达式,并通过优先级处理逻辑和算术运算。详细讲解了表达式拆解和数据计算的关键步骤,包括括号处理、非运算、逻辑和算术运算的计算。此外,还提供了代码片段展示了如何计算表达式值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

支持   "加[+]、减[-]、乘[*]、除[/]、商[$:两整数相除,结果的整数部分]、模[%]、括号[()]"四则混合运算,支持"与[&]、或[|]、异或[^]、左移[<   ]、右移[   >]和非[!]"逻辑运算功能,同时它们可以出现在同一个表达式中,它们的优先级依次为括号、非、与或异或左右移、乘除商模、加减。如式:12.45+3*16   >2*(3+6*(3+2)-1)=12.45+3*4*32,计算结果为:396.45。

程序包括两大部分功能:表达式拆解、因子计算,分别由两个类TBdsProc和TPUSHPOP完成。具体如下:  
   
  CDIKind=record  
      case   id:   Boolean   of  
      True:   (dval:   Double);  
      False:   (ival:   Integer);  
  end;  
   
  CDKind:区别表达式中的整数和浮点数类型,  
   
  因为有些运算符不支持浮点数(如逻辑运算)。  
   
      ValKind   =   CDIKind;  
      TBdsProc   =   class  
  private  
      Fghpd   :   Integer;//识别并标记左右括号是否成对出现  
      function   IsCalcFh(c:   Char):   boolean;//判别一个字符是否运算符  
      function   CopyRight(abds:   String;start:   Integer):String;//截取字符串表达式  
      function   BdsSs(var   abds:   String):   ValKind;//返回一个子表达式的值  
      function   BdsYz(var   abds:   String):   ValKind;//表达式因子,如:15、(13+5)  
      function   BdsItm(var   abds:   String):   ValKind;//读取表达式中的一个因子  
  public  
      function   CalcValue(const   bds:   String):   ValKind;//返回计算结果  
  end;  
   
  TPUSHPOP   =   class  
  private  
      ffh:   array   [0..2]   of   Char;//符号数组  
      value:   array   [0..3]   of   CDIKind;//值数组  
      flevel:   Byte;//因子个数  
      fisfh:   Boolean;//识别等待输入值或运算符  
      fisnot:   Boolean;//识别待计算数据项是否执行非运算  
      function   Calcsj(av1,av2:   CDIKind;fh:   Char):   CDIKind;     //执行两个数值的四则运算  
      function   Calclg(av1,av2:   CDIKind;   fh:   Char):   CDIKind;//执行两个数的逻辑运算  
      procedure   Calccur;{当输入数据项满足四个数后[依运算优先级层数求得,见下述算式解析原理]执行中间运算}  
      function   IsLgFh(fh:   Char):   Boolean;//一个符号是否逻辑运算符  
      function   IsCcFH(fh:   Char):   Boolean;//   一个符号乘除商模运算符  
  public  
      constructor   Create;  
      procedure   PushValue(avalue:   CDIKind);//存入一个数据项  
      procedure   PushFh(afh:   Char);//存入一个符号  
      function   CalcValue:   CDIKind;//计算并返回值  
  end;  
  //   表达式解析基本原理:  
  //   1.表达式处理:  
  //   表达式的一个个数据项组成,中间由运算符连接,每个数据项为一个分析基本分析单元。表达式中如果包含有改变运算优先级别的括号运算,先计出括号中式子的值,再把该值当一个数据项处理,这一点在程序设计中只要运用递归功就能实现。  
  //   2.数据项计算处理  
  //   a   >非运算:  
  //   它为单目运算符,级别最高,在存入符号时做标记,存入数据时即时计算并去除标记。  
  //   b   >表达式运算:  
  //   设f1、f2、f3分别表示一二三级运算符,V1、V2、V3、V4分别表示顺序四个数,则极端表达式模型为R=V1   f1   V2   f2   V3   f3   V4   …,计算时顺序应为   R=…V4   f3   V3   f2   V2   f1   V1。为了简化运算,把其中运算级别最高的逻辑运算在存入数据时先计算完成,   初始化时设V1=0,第一个运算符设为'+'。则公式化为:   R=…V4   f2(f1)   V3   f2(f1)   V2   f1   V1。这样,当V2与V3间的运算符级别为f2时,V4与V3间的运算符级别<   =f2,则:V2   =(V2与V3计算值),V3后的值和运算符前移;若V2与V3间的运算级别为f1,可先算V1与V2,V2以后的值和运算符前移。则计算后的表达式为:R=V3   f2(f2)   V2   f1   V1刚好满足循环取数条件。  
  //   3.实现:  
  //   程序比较长(TBdsProc和TPUSHPOP的源代码合计长度为400多行),完整代码见附件,以下对一些重要实现方法做介绍:  
  //   <   1   >表达式拆解:由方法BdsSs和BdsYz完成表达式拆解和因子处理  
  function   TBdsProc.BdsSs(var   abds:   String):   ValKind;  
  var  
      c:   Char;  
      lpp:   TPushPop;  
  begin  
      lpp   :=   TPushPop.Create;//建立数据计算对象  
      while   abds<   >''   do   begin  
          c   :=   abds[1];  
          if   IsCalcFh(c)   then   begin//是否运算符  
              lpp.PushFh(c);//保存运算符  
              abds   :=   CopyRight(abds,2);  
          end   else   begin  
              if   c=')'   then   begin  
                  Dec(Fghpd);//括号匹配  
                  abds   :=   CopyRight(abds,2);  
                  if   Fghpd   <   0   then   Raise   Exception.Create('括号不配对');  
                  Result   :=   lpp.CalcValue;//返回括号中的子项值,进行下一步计算  
                  lpp.Free;  
                  Exit;  
              end   else   begin  
                  if   c='('   then   Inc(Fghpd);//做括号层数标识  
                  lpp.PushValue(BdsYz(abds));//取下一项的值。  
              end;  
          end;  
      end;  
      if   Fghpd<   >0   then   Raise   Exception.Create('括号不配对');  
      Result   :=   lpp.CalcValue;//返回最终运算值  
      lpp.Free;  
  end;  
   
  function   TBdsProc.BdsYZ(var   abds:   String):   ValKind;  
  begin  
      if   abds<   >''   then   begin  
          if   abds[1]='('   then   begin  
              abds   :=   CopyRight(abds,2);  
              Result   :=   BdsSs(abds);//递归调用,求括号中的值  
          end   else   Result   :=   BdsItm(abds);{读一个数据项,如果包括函数定义,可以在该方法中定义接口或连接}  
      end;  
  end;  
   
  若表达式要支持函数功能,只要定义系统支持的函数,在取行表达式因子BdsItm中加入连接函数定义即可,如下:  
  …..  
  else   if   (c<   ='Z')   and   (c   >='A')   then   begin  
      bhs   :=   取出函数名及参数  
      Result   :=   …   //调用函数处理   (函数定义)  
      abds   :=   下一项  
  end  
  ….  
   
  <   2   >   数据计算:主要包PushValue,PushFh,Calccur分别完成存入数、符号、中间计算  
  procedure   TPUSHPOP.PushValue(avalue:   CDIKind);  
  begin  
      if   fisfh=True   then   Raise   Exception.Create('缺少运算符');  
      if   fisnot   then   begin//进行非运算  
          if   avalue.id   then   Raise   Exception.Create('浮点数不能做非运算');  
          avalue.ival   :=   not   avalue.ival;  
          fisnot   :=   False;  
      end;  
      if   IsLgFh(ffh[flevel])   then   begin//运行逻辑运算  
          value[flevel]   :=   Calclg(value[flevel],  
          avalue,ffh[flevel]);//与当前值做逻辑运算  
      end   else   begin  
          Inc(flevel);//存数位置指针加1  
          value[flevel]   :=   avalue;//存入值  
          if   flevel   >2   then   Calccur;//数据个数达到4,进行中间运算  
      end;  
      fisfh   :=   True;//输入符号可见  
  end;  
   
  procedure   TPUSHPOP.PushFh(afh:   Char);  
  begin  
      if   (fisfh=false)   then   begin//非运算是一级  
          if   (afh='!')   and   (not   fisnot)   then   begin//标识非运算  
              fisnot   :=   True;  
              Exit;  
          end   else   Raise   Exception.Create('运算符重复');  
      End   Else   begin  
          ffh[flevel]   :=   afh;//存入运算符  
          fisfh   :=   False;   输入值可见  
      end;  
  end;  
   
  procedure   TPUSHPOP.Calccur;  
  begin  
      if   IsCcFh(ffh[1])   then   begin//二级运算符  
          value[1]   :=   Calcsj(value[1],value[2],ffh[1]);//计算2和3项的值  
          ffh[1]   :=   ffh[2];//后序运符和值前移  
          value[2]   :=   value[3];  
      end   else   begin//一级运算符  
          value[0]   :=   Calcsj(value[0],value[1],ffh[0]);//计算1和2项的值  
          value[1]   :=   value[2];{   后序运符和值前移,2和3项的值不计算是为了保持第一个运算符为一级运算符}  
          value[2]   :=   value[3];  
          ffh[0]   :=   ffh[1];  
          ffh[1]   :=   ffh[2];  
      end;  
      Dec(flevel);//存数位置指针减1  
  end;


function   Calcalute(Express:   String):   Double;    
  //Calcalute   Expression   Value  
  //Created   :   549@17:19   2004-02-28  
  //Modified:   549@14:57   2004-06-19  
      function   MulDivAddSub(var   Express:   String):   Double;  
      //mul   and   div   first,   then   add   and   sub  
          function   ProcessAtPos(var   Express:   String;   Var   Pos:   Integer):   Double;  
          var  
              Left:   Integer;  
              Right:   Integer;  
              Num1,Num2:Double;  
          begin  
              Right   :=   Pos   +   2;  
              Left   :=   Pos   -   1;  
              Result   :=   0;  
              while   ((Express[Left]   >=   '0')   and   (Express[Left]   <=   '9')   or   (Express[Left]   =   '.'))   do  
                  if   Left   >   0   then   dec(Left)  
                  else  
                      Break;  
              while   ((Express[Right]   >=   '0')   and   (Express[Right]   <=   '9')   or   (Express[Right]   =   '.'))   do  
                  if   Right   <=   Length(Express)   then   Inc(Right)  
                  else  
                      Break;  
              if   (Express[Right]   <>   '')   then   Dec(Right);  
              if   (Express[Left]   =   '-')   and   (Left   =   1)   then   begin  
                  Dec(Left);  
              end;  
              try  
                  Num1   :=   StrToFloat(Copy(Express,Left+1,Pos-Left-1));  
              except  
                  Num1   :=   0;  
              end;  
              try  
                  Num2   :=   StrToFloat(Copy(Express,Pos   +   1,Right-Pos));  
   
              except  
                  Num2   :=   0;  
              end;  
              if   Express[pos]   =   '*'   then  
                  Result   :=   Num1   *   Num2  
              else   if   Express[pos]   =   '/'   then  
                  Result   :=   Num1   /   Num2  
              else   if   Express[pos]   =   '+'   then  
                  Result   :=   Num1   +   Num2  
              else   if   Express[pos]   =   '-'   then  
                  Result   :=   Num1   -   Num2;  
              if   Result   <>   0   then  
                  Express   :=   LeftStr(Express,Left)   +   VarToStr(Result)   +   RightStr(Express,Length(Express)   -   Right);  
              if   Express[1]   =   '('   then  
                  Express   :=   RightStr(Express,   Length(Express)   -1);  
              //relocate   Pos  
              if   ((Left   =   0)   or   (Left   >   Length(Express)))   then  
                  Left   :=   1;  
              Pos   :=   Length(VarToStr(Result))   +   Left   -   1;  
              //showmessage('Left:'+VarToStr(Left)+#13+'Right:'+VarToStr(Right)+Format('Exchange   %f   and   %f',[Num1,Num2])+'Result   :   '+Express+#13+'Pos   is:'+VarToStr(Pos)+#13+'Value   at   pos   :   '+Express[Pos]);  
   
          end;  
   
      var  
          i:   Integer;  
          HaveAResult:   Boolean;  
      begin  
          HaveAResult   :=   False;  
          //mul   and   div   first   ,   add   and   sub   second  
          while   not   HaveAResult   do   begin  
              for   i   :=   1   to   Length(Express)   do   begin  
                  case   Express[i]   of  
                      '*','/':   ProcessAtPos(Express,i);  
                  end;  
              end;  
              for   i   :=   1   to   Length(Express)   do   begin  
                  case   Express[i]   of  
                      #0           :   Break;  
                      '+','-':   ProcessAtPos(Express,i);  
                  end;  
              end;  
            //if   exist   '+',   '-',   '*',   '/',   process   it  
            for   i   :=   Length(Express)   downto   1   do   begin  
                  case   Express[i]   of  
                      '+','-','*','/':   Break;  
                  end;  
              end;  
              HaveAResult   :=   (i   =   0)   or   ((i   =   1)   and   ((Express[1]   =   '+')   or   (Express[1]   =   '-')));  
   
          end;  
          Result   :=   StrToFloat(Express);  
      end;  
      function   CheckExpression(var   Express:   String):   Boolean;  
      //Check   syntax  
      var   i:   Integer;  
              Left:   Integer;  
              TmpStr:   String;  
      begin  
          Result   :=   False;  
          Left   :=   1;  
          if   Express   =   ''   then   Exit;  
          //Delete   blank  
          TrimAll(Express);  
          //Check   illegal   character   and   bracket   matching  
          for   i   :=   1   to   Length(Express)   do   begin  
              case   Express[i]   of  
                  '0'..'9','+','-','*','/','.':   Continue;  
                  '(':   Inc(Left);  
                  ')':   Dec(Left);  
              else  
                  Exit;  
              end;  
              if   Left   <   1   then   Exit;  
          end;  
          Result   :=   Left   =   1;  
      end;  
      function   Calc(Express:   String):   Double;  
      //Calculate   expression  
      var   Right:   Integer;  
              Left:   Integer;  
              Para:   String;  
              Temp:   Double;  
      begin  
          Result   :=   0;  
          if   Express   =   ''   then   Exit;  
          //Begin   Calculate  
          while   True   do   begin  
              for   Right   :=   1   to   Length(Express)   do   begin  
                  case   Express[Right]   of  
                  ')',#0:   Break;  
                  end;  
              end;  
              for   Left   :=   Right   downto   1   do   begin  
                  case   Express[Left]   of  
                  '(':   Break;  
                  end;  
              end;  
              if   Left   <>   0   then   begin  
                  //bracket   first  
                  Para   :=   Copy(Express,Left+1,Right-Left-1);  
                  Temp   :=   MulDivAddSub(Para);  
                  Express   :=   LeftStr(Express,Left-1)   +   VarToStr(Temp)   +   RightStr(Express,Length(Express)   -   Right);  
              end  
              else  
              if   Right   =   Length(Express)   +   1   then   begin  
                  //no   bracket,   mul   and   div   first,   then   add   and   sub  
                  Result   :=   MulDivAddSub(Express);  
                  Exit;  
              end;  
   
          end;  
      end;  
  begin  
      Result   :=   0;  
      if   not   CheckExpression(Express)   then   Exit;  
      Result   :=   Calc(Express);  
  end;  
  //在使用前,先把你的变量替换成相应的数值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值