SOURCE

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 命令行工具 X clear

                    
>
console