• 一款设计精巧的表达式解析器一款设计精巧的表达式解析器


    一款设计精巧的表达式解析器   
    ----   开发MIS系统时,报表设计中经常会碰到表达式解释器,完成用户自定义的公式运算。这种程序的设计需要有比较高的技巧,以下介绍一款用DELPHI4.0开发的程序[程序重在算法,语言特性很少,用其它语言的人也能读懂],只要按自已的要求稍加修改,即可做成组件或全局方法发部。它支持   "加[+]、减[-]、乘[*]、除[/]、商[$:两整数相除,结果的整数部分]、模[%]、括号[()] "四则混合运算,支持 "与[&]、或[|]、异或[^]、左移[ <   ]、右移[   > ]和非[!] "逻辑运算功能,同时它们可以出现在同一个表达式中,它们的优先级依次为括号、非、与或异或左右移、乘除商模、加减。如式: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//数据个数达到4,进行中间运算 
          Calccur; 
      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; 
  • 相关阅读:
    centos7 安装redis服务及phpredis扩展
    centos7 LANMP 安装
    php reids 单机命令
    浅谈正向代理和反向代理
    php-fpm日志告警"seem busy"
    PHP-redis中文文档
    Nginx配置文件nginx.conf中文详解
    生成带参数的微信二维码
    微信临时二维码时为32位非0整型
    [Linux]Service mysql start出错(mysql: unrecognized service)解决方法
  • 原文地址:https://www.cnblogs.com/ljl_falcon/p/2278221.html
Copyright © 2020-2023  润新知