function ConditionParser() {
this.stacks = []
this.opsMap = {
'||': 1,
'&&' : 2,
'>': 3,
'>=': 3,
'<': 3,
'<=': 3,
'=': 3,
'+': 4,
'-': 4,
'*' : 5,
'/': 5,
'%': 5
}
this.PHReg = /#\{?([a-zA-Z-\\\/.\d]+)\}?(?:\.includes\((.+)\))?/
this.ops = Object.keys(this.opsMap)
}
ConditionParser.prototype.eval = function (exp, obj) {
const tokens = this.getToken(exp)
const rebuildTokens = this.transform(tokens)
const result = this.compute(rebuildTokens, obj)
// console.log(`${exp} = ${result}`, rebuildTokens)
return result
}
ConditionParser.prototype.readPlaceholder = function(exp, tokens) {
let char = exp[0]
if (char === '#') {
if (this.PHReg.test(exp)) {
const ph = RegExp['$&']
tokens.push({
type: 'placeholder',
value: RegExp.$1,
includes: RegExp.$2
})
exp = exp.substring(ph.length)
}
}
return exp
}
ConditionParser.prototype.readCompareOp = function(exp, tokens) {
let i = 0
let char = exp[i]
const list = [char]
if (['>', '<'].includes(char)) {
char = exp[i + 1]
if (char === '=') {
list.push(char)
i += 2
} else {
i += 1
}
const val = list.join('')
tokens.push({
type: 'op',
value: val,
priority: this.opsMap[val]
})
exp = exp.substring(i)
} else if (char === '=') {
i += 1
tokens.push({
type: 'op',
value: char,
priority: this.opsMap[char]
})
exp = exp.substring(i)
}
return exp
}
ConditionParser.prototype.readLogic = function(exp, tokens) {
let i = 0
let char = exp[i]
const list = [char]
if (['|', '&'].includes(char)) {
char = exp[i + 1]
if (['|', '&'].includes(char) && char === list[0]) {
list.push(char)
i += 2
const val = list.join('')
tokens.push({
type: 'op',
value: val,
priority: this.opsMap[val]
})
} else {
i += 1
}
exp = exp.substring(i)
}
return exp
}
ConditionParser.prototype.readNumber = function(exp, tokens) {
if (/^(\d+(\.\d+)?)/.test(exp)) {
const numVal = RegExp.$1
tokens.push({
type: 'number',
value: numVal
})
exp = exp.substring(numVal.length)
}
return exp
}
ConditionParser.prototype.readOp = function(exp, tokens) {
const char = exp[0]
tokens.push({
type: 'op',
value: char,
priority: this.opsMap[char]
})
exp = exp.substring(1)
return exp
}
ConditionParser.prototype.readCurly = function(exp, tokens) {
const char = exp[0]
tokens.push({
type: 'curly',
value: char,
priority: this.opsMap[char]
})
exp = exp.substring(1)
return exp
}
ConditionParser.prototype.readString = function(exp, tokens) {
if (/\s*(['"])([^'"]+)\1/.test(exp)) {
tokens.push({
type: 'string',
value: RegExp.$2
})
exp = exp.substring(RegExp['$&'].length)
} else if (/\s*([^ \+\-\*\/%><=&|]+)/.test(exp)) {
tokens.push({
type: 'string',
value: RegExp.$1
})
exp = exp.substring(RegExp['$&'].length)
} else {
exp = exp.substring(1)
}
return exp
}
ConditionParser.prototype.getToken = function(exp) {
this.i = 0
if (!exp) {
return []
}
const tokens = []
let placeholder = []
while (exp.length) {
const char = exp[0]
if (char == '#') {
exp = this.readPlaceholder(exp, tokens)
} else if (['>', '<'].includes(char)) {
exp = this.readCompareOp(exp, tokens)
} else if (['|', '&'].includes(char)) {
exp = this.readLogic(exp, tokens)
} else if (/\d/.test(char)) {
exp = this.readNumber(exp, tokens)
} else if (this.ops.includes(char)) {
exp = this.readOp(exp, tokens)
} else if (['(',')'].includes(char)) {
exp = this.readCurly(exp, tokens)
} else if (!/\s/.test(char)) {
exp = this.readString(exp, tokens)
} else {
exp = exp.substring(1)
}
}
return tokens
}
ConditionParser.prototype.transform = function(tokens) {
const results = []
const opStack = []
let i = 0
while(i < tokens.length) {
const token = tokens[i]
if (['placeholder', 'number', 'string'].includes(token.type)) {
results.push(token)
} else if (this.ops.includes(token.value)) {
let top = opStack[opStack.length - 1]
while(top && top.priority >= token.priority) {
results.push(top)
opStack.pop()
top = opStack[opStack.length - 1]
}
opStack.push(token)
} else if (token.type === 'curly') {
if (token.value === '(') {
opStack.push(token)
} else {
let top = opStack.pop()
while(top && top.value !== '(') {
results.push(top)
top = opStack.pop()
}
if (!top) {
return null
}
}
}
i += 1
}
if (opStack.length) {
let top = opStack.pop()
while(top) {
if (top.type === 'curly') {
return null
}
results.push(top)
top = opStack.pop()
}
}
return results
}
ConditionParser.prototype.getKeyPathValue = function(keypath, obj) {
const keys = (keypath || '').split('.')
const val = keys.reduce((prev, key) => {
return prev && prev[key]
}, obj)
return val
}
ConditionParser.prototype.compute = function (tokens, obj) {
const newTokens = [...tokens]
let result = []
while(newTokens.length) {
const token = newTokens.shift()
if (token.type === 'placeholder') {
let val = this.getKeyPathValue(token.value, obj)
let other = token.includes
if (other) {
if (this.PHReg.test(other)) {
other = this.getKeyPathValue(RegExp.$1, obj)
} else if (/(['"])(.*)\1/.test(other)) {
other = RegExp.$2
}
if (!val.map) {
val = [val]
}
val = val.some(i => i==other)
}
result.push(val)
} else if (['number', 'string'].includes(token.type)) {
result.push(token.value)
} else if (result.length >= 2) {
let right = result.pop()
let left= result.pop()
const val = this.calc(left, right, token.value)
if (val == null) {
return null
}
result.push(val)
} else {
return null
}
}
if (result.length > 1) {
return null
}
const value = result[0]
return value
}
ConditionParser.prototype.calc = function(a, b, op) {
const isNumber = (val) => {
if (/^[+-]?\d+(\.\d+)?$/.test(val)) {
return true
}
return false
}
const needNumber = ['+', '-', '*', '/', '%', '>', '>=', '<', '<='].includes(op)
if (needNumber) {
if (!isNumber(a) || !isNumber(b)) {
return null
}
a = Number(a)
b = Number(b)
}
if (op === '*') {
return a * b
} else if (op === '/') {
if (b > 0) {
return a / b
} else {
return null
}
} else if (op === '%') {
return a % b
} else if (op === '+') {
return a + b
} else if (op === '-') {
return a - b
} else if (op === '>') {
return a > b
} else if (op === '>=') {
return a >= b
} else if (op === '<') {
return a < b
} else if (op === '<=') {
return a <= b
} else if (op === '&&') {
return a && b
} else if (op === '||') {
return a || b
} else if (op === '=') {
return a == b
}
return null
}
const parser = new ConditionParser()
const condition1 = `#{name} = lw1t@364.ccom && #age >= 234`
const formModel1 = {
age: 234,
name: 'lw1t@364.ccom'
}
let result = parser.eval(condition1, formModel1)
console.log(`测试1:`, result)
const condition2 = `#{roles}.includes('manager') && #price > 1000`
const formModel2 = {
roles: ['admin', 'manager', 'normal'],
price: 1200
}
result = parser.eval(condition2, formModel2)
console.log(`测试2结果:`, result)
const condition3 = `#{price} * #{ratio} + 100`
const formModel3 = {
price: 150,
ratio: null
}
result = parser.eval(condition3, formModel3)
console.log(`测试3结果:`, result)
console