最近项目涉及到表达式,简单封装了一些功能,如表达式解析、执行等,解析不用说,网上很多表达式字符串解析为词法树的库,我们也不必再造轮子(我用jsep进行解析),这里说下执行表达式的方式。

默认字符串解析抽象词法树按理说是可以直接解析执行的,只不过通常此类词法树要表达的内容过于多,数据的存储也过于庞大,于是我们结合自己业务对关键数据做了抽象,比如

// 字符串
a && b
// 解析完
{
    type: "LogicalExpression",
    operator: "&&",
    left: {type: "Identifier", name: "a"},
    right: {type: "Identifier", name: "b"}
}
// 而我们需要的关键信息就是:表达式包含a、b并且他们两个中间是&&,于是改造一下
['&&', 'a', 'b']
//是不是简洁多了

下面实现个表达式执行函数的map枚举

const rulesMap = {
    '===': (left, right) => left === right,
    '!==': (left, right) => left !== right,
    '<': (left, right) => left < right,
    '<=': (left, right) => left <= right,
    '>': (left, right) => left > right,
    '>=': (left, right) => left >= right,
    '&&': (left, right) => !!(left && right),
    '||': (left, right) => !!(left || right),
    '!': value => !value
}
// 如果不想直接按程序语言,key完全可以根据爱好来

既然是我们自己定义的表达式,就可以实现一个校验

function isValidCondition(expression) {
    return Array.isArray(expression) && !!rulesMap[expression[0]]
}

表达式可能存在变量,支持一下业务数据的上下文替换(我们变量以@开头)

const get = require('lodash/get')
const rVariable = /^@(.+)/

function replaceVariable(variable, context) {
    if (context && typeof variable === 'string' && rVariable.test(variable)) {
        const key = variable.substr(1)
        if (Array.isArray(context)) {
            for (let i = 0; i < context.length; i++) {
                const value = get(context[i], key)
                if (value !== undefined) {
                    return value
                }
            }
        } else {
            return get(context, key)
        }
    }
    return variable
}

最后就是执行入口

function callExpression(type, args) {
    if (rulesMap[type]) {
        return rulesMap[type](...args)
    }
    throw new Error(`no support expression type \`${type}\``)
}

是不是缺点什么?对咯,表达式可能嵌套

function canBreak(type, value) {
    return (type === '||' && value) || (type === '&&' && !value)
}

function exec(expression, context) {
    if (isValidCondition(expression)) {
        const value = []
        const type = expression[0]
        for (let i = 1; i < expression.length; i++) {
            if (isValidCondition(expression[i])) {
                value.push(exec(expression[i], context))
            } else {
                value.push(replaceVariable(expression[i], context))
            }
            if (value[0] !== undefined && canBreak(type, value[0])) {
                value.push(value[0])
                break
            }
        }
        return callExpression(type, value)
    }
    return false
}

前面几个函数都用上了,简单高效(不直接存储a && b,而是存储[‘&&’,’a’,’b’],达到运行时不用解析字符串的目的,从而在执行层面提高效率)的表达式运行函数编写完成。