Post

后缀表达式与执行器

最近在项目中看到了一个需求,导出的word报表需要一些统计数据。

数据来源是excel。 excel已经导入到数据库中,所以这些统计数据可以有一下办法完成:

3种办法:

  • sql来聚合执行并查询结果
  • 使用执行引擎来做
  • 使用if/else来解析计算公式

项目目前使用的第三种,开发方便但是不太好维护,因为excel表格变更或则移位后就需要重新更新代码。

所以我想着之前学过编译器原理,能不能搞一个执行引擎出来。 写了半天,出了一个demo, 备注在这里以备后用:

以下代码主要作用为解析表达式为可执行的波兰表达式。

项目中根据变量名到数据库取数据然后进行计算,这样再复杂的统计也只需要配置公式即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

<html>
    <script>

var a = "1+2-3*4+(5*(6+1))"
a = "1+2-3*4+(5*(6/2+1))"
a = "1+2-3*4"


//构建为后缀表达式
var ex = buildExpression(a);

// 使用执行器解析执行后缀表达式
console.log(calc(ex))


function calc(expresion) {
    var isop = {
        "+":1,
        "-":1,
        "*":2,
        "/":2
    }

    var stack  = []
    for(var i =0; i<expresion.length; i++) {
        stack.push(expresion[i]);
        if(isop[expresion[i]] != undefined) {
            var op = stack.pop();
            var v1 = stack.pop();
            var v2 = stack.pop();

            if(op == "+") {
                stack.push(Number(v2) + Number(v1))
            }
            if(op == "-") {
                stack.push(Number(v2) - Number(v1))
            }
            if(op == "*") {
                stack.push(Number(v2) * Number(v1))
            }
            if(op == "/") {
                stack.push(Number(v2) / Number(v1))
            }
        }

    }
    return stack[0]

}

/**
 * 将表达式构建为后缀表达式
 * 
 * 
 **/
function buildExpression(a) {
    var res = ""
    var stack = []
    var levels = {
        "+":4,
        "-":4,
        "(":1,
        ")":1,
        "*":8,
        "/":8
    }

    for(var i =0; i<a.length; i++) {
        var i1 = a[i];
        
        if(/^\d$/.test(i1)) {
            res += i1;
        } else {

            if(i1 == "(") {
                stack.push({
                    symbol:i1,
                    level: levels[i1],
                    pop: false
                })
            }
            if(i1 == ")") {
                
                var tmp;
                while((tmp = stack.pop()) != undefined) {
                    if(tmp.symbol == "(") {
                        break
                    }
                    res += tmp.symbol
                }
    
            }
            if(i1 == "+" || i1 == "-" || i1 == "*" || i1 == "/") {
                var level = levels[i1]
                for(var j=stack.length -1; j>=0 && stack.length >0; j--) {
                    if(stack[j].symbol == "(") {
                        break
                    }
                    if(stack[j].pop == false && stack[j].level >= level) {
                        stack[j].pop = true
                        res += stack[j].symbol
                    }
                }

                var tmps = []
                for(var j=0; j<stack.length && stack.length >0; j++) {
                    if(stack[j].pop == false ) {
                        tmps.push(stack[j])
                    }
                }
                tmps.push({
                    symbol:i1,
                    level: levels[i1],
                    pop: false
                })

                stack = tmps;

            }
        }

    }

    var tmp;
    while((tmp = stack.pop()) != undefined) {
        res += tmp.symbol
    }
    
    return res

}


    </script>
</html>


This post is licensed under CC BY 4.0 by the author.