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)}

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

  • 相关阅读:
    使用javap分析Java的字符串操作
    使用javap深入理解Java整型常量和整型变量的区别
    分享一个WebGL开发的网站-用JavaScript + WebGL开发3D模型
    Java动态代理之InvocationHandler最简单的入门教程
    Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
    Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
    Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
    Java实现 LeetCode 541 反转字符串 II(暴力大法)
    Java实现 LeetCode 541 反转字符串 II(暴力大法)
    Java实现 LeetCode 541 反转字符串 II(暴力大法)
  • 原文地址:https://www.cnblogs.com/csguo/p/7614884.html
Copyright © 2011-2022 走看看