-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathexpression.js
445 lines (431 loc) · 13.6 KB
/
expression.js
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/*!
* 逆波兰表达式库--全功能版本
* RPN-expression JavaScript Library
*
*
* Copyright lupc-贺兰
* Released under the MIT license
*
* Date: 2018-04-13
*/
var TOKEN_NUMBER = "number";
var TOKEN_OPERATOR = "operator";
var TOKEN_IDENTIFIER = "identifier";
var TOKEN_EOF = "eof";
function isOperator(c) { return /[+\-*\/\^%=()]/.test(c); };
function isDigit(c) { return /[0-9]/.test(c); };
function isAlphaOrLine(c) { return /[a-zA-Z_\$]/.test(c); };
function isAlphaOrLineOrNumber(c) { return /[0-9a-zA-Z_\$]/.test(c); };
function isWhiteSpace(c) { return /\s/.test(c); };
//获取运算符优先级,数值越大,优先级越低
function getPriority(op){
var _op = op.value;
if(_op == "(")
return 0;
else if(_op == ")")
return 10;
else if(_op == "*")
return 30;
else if(_op == "/")
return 31;
else if(_op == "%")
return 32;
else if(_op == "+")
return 40;
else if(_op == "-")
return 41;
return 0;
};
//比较运算符优先级
function comparePriority(opa,opb){
var a = getPriority(opa);
var b = getPriority(opb);
if(a > b)
return -1;
else if(a == b)
return 0;
else
return 1;
};
function CalContext(){
if(this instanceof CalContext){
this.dataMap = {};
} else {
throw new "你必须用new关键字调用构造函数";
}
};
CalContext.prototype.calc = function(expression){
//
this.charIndex = 0;
//表达式转换
var finalTokens = this._expr(expression);
this.finalTokens = finalTokens;
//执行
var result = this._execute(finalTokens);
return result;
};
//
CalContext.prototype._next = function(expression){
var c,that = this;
var createToken = function(type,value){
var token = {
type: type,
value: value
};
that.currentToken = token;//记录当前的token
return token;
};
while(this.charIndex < expression.length){
c = expression[this.charIndex];
if(isDigit(c)){
var num = parseInt(c);
var num2 = 0;//如果是浮点数,先用此数记录小数后的部分
var isFloat = false;
this.charIndex++;
while(this.charIndex < expression.length){
c = expression[this.charIndex];
if(isFloat) {
if(isDigit(expression[this.charIndex])){
this.charIndex++;
if(num2 == 0){
num2 = parseInt(c);
} else {
num2 = num2*10 + parseInt(c);
}
if(this.charIndex == expression.length){
num = parseFloat(num + "." + num2);
}
} else {
num = parseFloat(num + "." + num2);
break;
}
} else {
if(isDigit(expression[this.charIndex])){
this.charIndex++;
num = num * 10 + parseInt(c);
} else if(c == "."){
this.charIndex++;
isFloat = true;
} else {
break;
}
}
}
return createToken(TOKEN_NUMBER,num);
} else if(c == ",") {
this.charIndex++;
return createToken(TOKEN_EOF,0);//逗号也作为终结符(2个表达式之间的分隔符)
} else if(isOperator(c)){
this.charIndex++;
return createToken(TOKEN_OPERATOR,c);
} else if(isWhiteSpace(c)){
this.charIndex++;
} else if(isAlphaOrLine(c)){//标识符(变量,函数,对象属性,对象方法)
var name = c;
this.charIndex++;
while(this.charIndex < expression.length){
c = expression[this.charIndex];
if(isAlphaOrLineOrNumber(c)){
this.charIndex++;
name = name + c;
} else if(c == "("){//函数
return createToken(TOKEN_IDENTIFIER,name);
} else if(c == ".") {//对象
this.charIndex++;//忽略对象属性直接的 点号
return createToken(TOKEN_IDENTIFIER,name);
} else {
return createToken(TOKEN_IDENTIFIER,name);
}
}
} else {
throw "非法字符" + c;
}
}
return createToken(TOKEN_EOF,0);
}
/*
中缀表达式转化为逆波兰表达式算法:
//1、从左至右扫描一中缀表达式。
//2、若读取的是操作数,则判断该操作数的类型,并将该操作数存入操作数堆栈
//3、若读取的是运算符
// (1) 该运算符为左括号"(",则直接存入运算符堆栈。
// (2) 该运算符为右括号")",则输出运算符堆栈中的运算符到操作数堆栈,直到遇到左括号为止。
// (3) 该运算符为非括号运算符:
// (a) 若运算符堆栈栈顶的运算符为括号,则直接存入运算符堆栈。
// (b) 若比运算符堆栈栈顶的运算符优先级高,则直接存入运算符堆栈。
// (c) 若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,并将当前运算符压入运算符堆栈
//4、当表达式读取完成后运算符堆栈中尚有运算符时,则依序取出运算符到操作数堆栈,直到运算符堆栈为空。
*/
CalContext.prototype._expr = function(expression){
var qcount = 0;//正括号的个数
var operandStack = [];//操作数堆栈
var addOperand = function(operand){
operandStack.push({
type: operand.type,
value: operand.value,
callee: operand.callee,
args: operand.args //函数参数列表
});
};
var operatorStack = [];//操作符堆栈
var addOperator = function(operator){
operatorStack.push({
type: operator.type,
value: operator.value
});
};
//
var curToken = null,curOperand,curOperator;
while( (curToken = this._next(expression)).type != TOKEN_EOF){
if(curToken.type == TOKEN_NUMBER){
curOperand = curToken;
addOperand(curOperand);
continue;
} else if(curToken.value == "("){
qcount++;//运算符括号计数
curOperator = curToken;
addOperator(curOperator);
continue;
} else if(qcount == 0 && curToken.value == ")") {//处理函数的括号(函数的正括号已经处理过)
break;
} else if(curToken.value == ")"){
//若当前运算符为右括号,则依次弹出运算符堆栈中的运算符并存入到操作数堆栈,直到遇到左括号为止,此时抛弃该左括号.
while(true){
var last = operatorStack[operatorStack.length - 1];
if(last.value == "("){
operatorStack.pop();
break;
} else {
addOperand(operatorStack.pop());
}
}
continue;
} else if(curToken.type == TOKEN_IDENTIFIER){//标识符
//如果是调用,则维护一个成员列表 + 一个函数【可选】
curOperand = curToken;
curOperand.callee = [];//调用者链
curOperand.args = [];//参数列表
curOperand.callee.push(curToken.value);
//循环处理属性调用
while(true) {
var cIndex = this.charIndex;// 记录当前字符位置
var nextToken = this._next(expression);
if(nextToken.type == TOKEN_IDENTIFIER) {
curOperand.callee.push(nextToken.value);
} else if(nextToken.value == "(") {
//循环处理 函数参数(每个参数都是一个独立的表达式)
while(true){
//参数表达式
var _fpTokens = this._expr(expression);
curOperand.args.push(_fpTokens);
//参数处理完毕则 退出循环
if(this.currentToken.value == ")")
break;
}
break;
} else {
this.charIndex = cIndex;//还原
break;
}
}
addOperand(curOperand);//当作一个特殊的操作数
} else { //处理操作符(包含括号)
curOperator = curToken;
if(operatorStack.length == 0){
addOperator(curOperator);
continue;
} else {
//取得操作符栈的最顶层操作符
var last = operatorStack[operatorStack.length - 1];
//对比操作符优先级
if(comparePriority(curOperator,last) == 1){
addOperator(curOperator);
continue;
} else {
//若当前运算符若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,直至运算符栈栈顶运算符低于(不包括等于)该运算符优先级,
//或运算符栈栈顶运算符为左括号
//并将当前运算符压入运算符堆栈。
while(operatorStack.length > 0){
if(comparePriority(curOperator,operatorStack[operatorStack.length - 1]) < 1
&& operatorStack[operatorStack.length - 1].value != "("){
//
qcount--;//运算符括号计数
addOperand(operatorStack.pop());
//
if(operatorStack.length == 0){
addOperator(curOperator);
break;
}
} else {
addOperator(curOperator);
break;
}
}
}
}
}
}
//转换完成,若运算符堆栈中尚有运算符时,
//则依序取出运算符到操作数堆栈,直到运算符堆栈为空
while(operatorStack.length > 0){
addOperand(operatorStack.pop());
}
//调整操作数栈中对象的顺序并输出到最终栈
var finalTokens = [];
while (operandStack.length > 0)
{
finalTokens.push(operandStack.shift());
}
return finalTokens;
};
/*
逆波兰表达式求值算法:
1、循环扫描语法单元的项目。
2、如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。
3、如果扫描的项目是一个二元运算符,则对栈的顶上两个操作数执行该运算。
4、如果扫描的项目是一个一元运算符,则对栈的最顶上操作数执行该运算。
5、将运算结果重新压入堆栈。
6、重复步骤2-5,堆栈中即为结果值。
*/
CalContext.prototype._execute = function(exprTokens){
var value;
var opa,opb;
var opds = [];
for (var i = 0; i < exprTokens.length; i++) {
var token = exprTokens[i];
if(token.type == TOKEN_NUMBER){
//如果为操作数则压入操作数堆栈
opds.push(token.value);
} else if(token.type == TOKEN_IDENTIFIER){
var result = this._getCallExpressionResult(token);
opds.push(result);
} else if(token.type == TOKEN_OPERATOR){//二目操作符
switch (token.value) {
case "+":
opa = opds.pop();
opb = opds.pop();
opds.push(opa + opb);
break;
case "-":
opa = opds.pop();
opb = opds.pop();
opds.push(opb - opa);
break;
case "*":
opa = opds.pop();
opb = opds.pop();
opds.push(opa * opb);
break;
case "/":
opa = opds.pop();
opb = opds.pop();
opds.push(opb / opa);
break;
case "%":
opa = opds.pop();
opb = opds.pop();
opds.push(opb % opa);
break;
default:
console.warn("不支持的操作符:" + token.value);
break;
}
}
}
if(opds.length == 1){
value = opds.pop();
}
return value;
}
CalContext.prototype._getCallExpressionResult = function(callToken){
var tail = null,obj = null;
for (var i = 0; i < callToken.callee.length; i ++) {
var name = callToken.callee[i];
if(i == 0) {
obj = this.dataMap[name];
tail = obj.data;
continue;
}
tail = tail[name];
}
//判断tail是变量,函数,对象属性,对象方法
if(typeof tail == "function") {
var args = [];
//计算参数
for (var i = 0; i < callToken.args.length; i++) {
var arg = this._execute(callToken.args[i]);
args.push(arg);
}
//执行
if(obj.data === tail) {//函数
//执行 函数
var result = tail.apply(obj.context || {},args);
return result;
} else {//对象方法
//执行 对象方法
var result = tail.apply(obj.data,args);
return result;
}
} else {
return tail;//变量 或 对象属性
}
}
//最终的语法栈
CalContext.prototype.getFinalTokens = function(){
return this.finalTokens;
};
//在值栈中添加变量,函数,对象(支持对象方法)
CalContext.prototype.putData = function(name,data,context){
this.dataMap[name] = {
data:data,
context:context
};
};
//清除值栈中所有的自定义数据——若不再使用表达式,最好调用此方法清理数据
CalContext.prototype.clearAll = function(){
this.dataMap = {};
};
CalContext.prototype.assertEqualChrome = function(expr,result){
var context = this;
console.group("测试样例:" + expr);
console.time("编译和执行");
var _result = context.calc(expr);
console.timeEnd("编译和执行");
if(result == _result)
console.log("计算结果%c[%s]:%s=%f","font-size:20px; color:green;","成功",expr,_result);
else
console.log("计算结果%c[%s]:%s=%f","font-size:20px; color:red;","失败",expr,_result);
console.log("语法栈:");
console.log(context.getFinalTokens());
console.assert(result == _result,"测试失败");
console.groupEnd();
}
CalContext.prototype.assertEqualBrower = function(expr,result){
var context = this;
console.log("测试样例:" + expr);
var _result = context.calc(expr);
if(result == _result)
console.log("计算结果[成功]:" + expr + "=" + _result);
else
console.log("计算结果[失败]:" + expr + ",实际值:" + _result + ",期望值:" + result);
console.log("语法栈:");
console.log(context.getFinalTokens());
console.assert(result == _result,"测试失败");
console.log("----------------------------");
}
CalContext.prototype.assertEqual = function(expr,result){
if(console == undefined && print == undefined) {
throw "当前环境不支持打印,请直接调用";
} else if(console && (console.group == undefined || console.time == undefined)) {
this.assertEqualBrower(expr,result);
} else if(console){
this.assertEqualChrome(expr,result);
} else {
throw "当前环境不支持打印,请直接调用";
}
};
//导出
if(typeof module === "undefined") {
} else {
module.exports = CalContext;
}