zoukankan      html  css  js  c++  java
  • 表达式解析引擎的设计

    前言

    表达式的计算是一个一般性的问题。在报表领域,经常会出现支持计算公式的需求。MS Office Excel中的函数计算就是一个很好的参考例子。

    本文提供一个表达式引擎的设计方案,能够满足报表领域的复杂计算要求。

    一个良好的表达式引擎应该支持基本的二元运算和函数调用,而且二元运算能够嵌套函数调用,函数调用也能够嵌套二元运算,比如:

    例子1:=2>1&&((Num(1)+2*Num((Num(2)+2)*3/Num(2)))/2>0||2>1)&&0>1,执行结果:0

    例子2:=Switch(Num(1),是, Num(0),否),执行结果:是

    例子3:=Switch(20<10,1 ,20>=10, 20 + 20*0.1),执行结果:22

    当然,实际的应用里,表达式中要支持变量的绑定,比如:

    例子1(计算税后金额):=Fields!数量.Value * Fields!单价.Value*(1 + Fields!税率.Value / 100)

    例子2(不同价格不同税后金额):=Fields!数量.Value*Fields!单价.Value*Switch(Fields!单价.Value<100, 1.1, Fields!单价.Value<500, 1.08, Fields!单价.Value<800, 1.07)

    至于常用的一元运算,能够通过函数调用就很方便的实现,例如:取非,Not(exp1)。

    表达式的定义

    要支持二元运算能够嵌套函数调用,函数调用也能够嵌套二元运算,需要采用解析器设计模式,定义数个表达式对象,用表达式对象树来描述表达式字符串。

    值表达式:用来描述一个常量或变量,例如:1.1,Fields!数量.Value,Parameters!经手人.Value,字符串

    二元表达式:用来描述一个二元运算,例如:exp1 + exp2,exp1

    函数表达式:用来描述一个函数调用,例如:Num(exp1),Sum(exp1), Switch(cond1, proc1, cond2, proc2…)

    运算符优先级

    运算符优先级很重要,决定了运算的顺序,特别是括号,能够改变表达式的运算顺序。

    优先级

    运算符

    说明

    -1

    (

    左括号

    -1

    )

    右括号

    -2

    ^

    幂运算

    -3

    *

    -3

    /

    -3

    %

    取余

    -4

    +

    -4

    -

    -5

    <

    小于

    -5

    <=

    小于等于

    -5

    >

    大于

    -5

    >=

    大于等于

    -6

    ==

    等于

    -6

    !=

    不等于

    -7

    &&

    逻辑与

    -8

    ||

    逻辑或

    表达式引擎的工作流程

    二元运算解析生成后缀表达式数组的过程

    中缀表达式解析成后缀表达式的方法如下,要借用一个临时堆栈stack,输出是后缀表达式数组output:

    (1)从右向左依次读取表达式字符串str。

    (2)如果str是操作数(常量或变量),输出到output。

    (3)如果str是运算符(含左右括号),则做以下判断:

        a) 如果str = '(',放入堆栈stack。

        b)如果str = ')',依次弹出堆栈stack中的运算符输出到output,直到遇到'('为止。

        c)如果str不是')'或者'(',那么就和堆栈stack顶点位置的运算符top做优先级比较。

          1)如果top是'('或者str优先级比top高,那么将str放入堆栈stack。

          2)如果str优先级低于或者等于top,那么输出top到output,然后将str放入堆栈stack。

    (4)如果表达式字符串已经读取完成,而堆栈stack中还有运算符时,依次由顶端输出到output。

    后缀表达式数组生成表达式对象树的过程

    计算后缀表达式的方法如下,要借用一个临时堆栈stack:

    (1)从左向右扫描后缀表达式数组,依次取出一个数组元素data。

    (2)如果data是表达式,就压入堆栈stack。

    (3)如果data是运算符,就从堆栈stack中弹出此运算符需要用到的表达式的个数(二元运算符需要2个),创建一个新二元表达式,然后把二元表达式压入堆栈stack。

    (4)如果数组处理完毕,堆栈stack中最后剩余的表达式就是最终结果。

    例如,表达式=(Num(1)+2*Num(Num(2)+2*3/Num(2)))/2,生成的后缀表达式数组如下:

    0:    Method{num, [Const(1)]}

    1:    Const(2)

    2:    Method{num, [Binary{+, Method{num, [Const(2)]}, Binary{/, Binary{*, Const(2), Const(3)}, Method{num, [Const(2)]}}}]}

    3:    *

    4:    +

    5:    Const(2)

    6:    /

    该后缀表达式数组生成的表达式对象树如下:

    Binary{/, Binary{+, Method{num, [Const(1)]}, Binary{*, Const(2), Method{num, [Binary{+, Method{num, [Const(2)]}, Binary{/, Binary{*, Const(2), Const(3)}, Method{num, [Const(2)]}}}]}}}, Const(2)}

    剩下的工作就是执行表达式对象树,输出结果。

  • 相关阅读:
    【计算机世界】467- XOR — 神奇的按位运算符
    记 · 复习知识 · 偶遇好玩的知识点
    【CSS】466- 一行 CSS 代码搞定响应式布局
    【Web技术】465- 关于前端埋点统计方案思考
    【CSS】464- 5种 CSS 浮动和清除浮动的方法
    简单易懂的 React useState() Hook 指南(长文建议收藏)
    java中的四类八种
    线程
    异常
    Aspx Ajax 调用 C#函数处理数据
  • 原文地址:https://www.cnblogs.com/csguo/p/7614884.html
Copyright © 2011-2022 走看看