function MathExp() {
this.ops = ['+', '-', '*', '/', '%', '(', ')']
this.priorityMap = {
'*': 2,
'/': 2,
'%': 2,
'+' : 1,
'-': 1
}
}
MathExp.prototype.compute = function (a, op, b) {
if (op == '+') {
return Number(a) + Number(b)
} else if (op == '-') {
return Number(a) - Number(b)
} else if (op == '*') {
return Number(a) * Number(b)
} else if (op == '/') {
return Number(a) / Number(b)
} else if (op == '%') {
return Number(a) % Number(b)
}
}
MathExp.prototype.parseToken = function(exp) {
const items = (exp || '').split('')
const tokens = []
let nums = []
let placeholder = []
const pushPlaceholder = () => {
if (placeholder.length) {
tokens.push({
type: 'placeholder',
value: placeholder.join('')
})
placeholder = []
}
}
for (let item of items) {
if (/[\d\.]/.test(item)) {
if (placeholder.length > 0 && item === '.') {
placeholder.push(item)
} else {
nums.push(item)
pushPlaceholder()
}
} else {
if (nums.length) {
tokens.push({
type: 'number',
value: Number(nums.join(''))
})
nums = []
}
if (this.ops.includes(item)) {
pushPlaceholder()
tokens.push({
type: 'op',
value: item,
priority: this.priorityMap[item] || 0
})
} else if (!/\s/.test(item)) {
placeholder.push(item)
}
}
}
if (nums.length > 0) {
tokens.push({
type: 'number',
value: Number(nums.join(''))
})
}
if (placeholder.length > 0) {
pushPlaceholder()
}
return tokens
}
MathExp.prototype.updateExp = function(exp) {
if (typeof exp === 'string') {
this.tokens = this.parseToken(exp)
} else {
this.tokens = this.parseToken((exp || []).map(i => i.value).join(''))
}
}
/**
* 将普通的数学表达式 转换成 逆波兰(Reverse Polish Notation)形式
* 这样做的目的是忽略掉运算符的优先级、括号的影响
*/
MathExp.prototype.dal2Rnp = function () {
const opStack = []
const output = []
let i = 0, cnt = this.tokens.length
let tmp = null
while(i < cnt) {
const token = this.tokens[i]
if (token.type === 'number' || token.type === 'placeholder') {
// 如果是数字 or 占位符 直接输出
output.push(token)
} else if (token.type === 'op') {
if (['(', ')'].includes(token.value)) {
if (token.value === '(') {
// 左括号 进入操作符栈
opStack.push(token)
} else {
tmp = opStack.pop()
while(tmp.value !== '(' && opStack.length > 0) {
output.push(tmp)
tmp = opStack.pop()
}
if (tmp.value !== '(') {
throw 'error: unmatched ('
}
}
} else {
while(opStack.length > 0 && token.priority <= opStack[opStack.length - 1].priority) {
tmp = opStack.pop()
output.push(tmp)
}
opStack.push(token)
}
}
i++
}
if (opStack.length > 0) {
while(opStack.length > 0) {
tmp = opStack.pop()
if (tmp.type === 'op' && ['(', ')'].includes(tmp.value)) {
throw 'error: unmatched ('
}
output.push(tmp)
}
}
return output
}
MathExp.prototype.evalRnp = function(queue, placeholderMap) {
const output = []
const tmpMap = placeholderMap || {}
while(queue.length) {
let tmp = queue.shift()
if (tmp.type === 'number') {
output.push(tmp.value)
} else if (tmp.type === 'placeholder') {
let pv = tmpMap[tmp.value]
pv = !pv && pv !== 0 ? 1 : pv
output.push(pv)
} else if (tmp.type === 'op') {
if (output.length < 2) {
throw 'error: operator lacked'
}
let second = output.pop()
let first = output.pop()
const result = this.compute(first, tmp.value, second)
output.push(result)
}
}
if (output.length > 1) {
throw 'invalid'
}
let val = output[0]
if (!val && val !== 0) {
throw 'invalid'
}
return val
}
MathExp.prototype.eval = function(exp, placeholderMap) {
this.updateExp(exp)
let result = null
try {
result = this.evalRnp(this.dal2Rnp(), placeholderMap)
} catch {
result = null
}
// result = this.evalRnp(this.dal2Rnp())
return result
}
// '1+3.14/3*(金额+23.1)'
const formulas = [
{ type: 'number', value: 1 },
{ type: 'op', value: '+' },
{ type: 'number', value: 3 },
{ type: 'number', value: '.' },
{ type: 'number', value: 1 },
{ type: 'number', value: 4 },
{ type: 'op', value: '/', priority: 2 },
{ type: 'number', value: 3 },
{ type: 'op', value: '*' },
{ type: 'op', value: '('},
{ type: 'placeholder', value: 'price', name: '金额' },
{ type: 'op', value: '+' },
{ type: 'number', value: 2 },
{ type: 'number', value: 3 },
{ type: 'number', value: '.' },
{ type: 'number', value: 1 },
{ type: 'op', value: ')'},
]
const exp = new MathExp()
let result = exp.eval(formulas, { price: 100 })
console.log('根据数组:', result)
result = exp.eval('1+3.14/3*(金额+23.1)', { '金额': 100 })
console.log('根据字符串:', result)
console