最近项目涉及到表达式,简单封装了一些功能,如表达式解析、执行等,解析不用说,网上很多表达式字符串解析为词法树的库,我们也不必再造轮子(我用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’],达到运行时不用解析字符串的目的,从而在执行层面提高效率)的表达式运行函数编写完成。
上一篇: 让input maxlength区分中英文 下一篇: 用Monaco Editor取代Ace Editor