基于 Nearley 实现的自定义公式编辑器
最近在做一个自定义公式的编辑器,可以支持基本运行和变量名称,同时支持CASE(判断),ROUND,CEIL,FLOOR运算,使用时用户将自己的自定义变量写入公式,规则校验通过之后只需要将特定格式的变量名进行替换即可成为一个标准的可计算的数学表达式,从而得到结果。
这里使用的 Nearley,一个js的解析器,通过自定义语法实现所需功能,以下是我实现的一个公式表达式的代码。
# 程序起点
main -> _ AS _ {% function(d) {return d[1]; } %}
# 括号,CASE 运算
P -> "(" _ AS _ ")" {% function(d) {return d[2]; } %}
| N {% id %}
| ("CASE("|"case(") _ COMP _ "," _ AS _ (";" _ COMP _ "," _ AS):* _ ")" {% function(d) {
let l = d[8].length;
if (d[2]) {
return d[6];
}
let i;
// 为每一个分号后面的数据做匹配
for (i = 0; i < l; i++) {
if (d[8][i][2]) {
return d[8][i][6];
}
}
return NaN;
} %}
# 乘法 和 除法
MD -> MD _ "*" _ P {% function(d) {return d[0]*d[4]; } %}
| MD _ "/" _ P {% function(d) {return d[0]/d[4]; } %}
| P {% id %}
# 加法 和 减法
AS -> AS _ "+" _ MD {% function(d) {return d[0]+d[4]; } %}
| AS _ "-" _ MD {% function(d) {return d[0]-d[4]; } %}
| MD {% id %}
# 比较
COMP -> AS _ ">" _ AS {% function(d) {return d[0] > d[4];} %}
| AS _ ">=" _ AS {% function(d) {return d[0] >= d[4];} %}
| AS _ "<" _ AS {% function(d) {return d[0] < d[4];} %}
| AS _ "<=" _ AS {% function(d) {return d[0] <= d[4];} %}
| AS _ "==" _ AS {% function(d) {return d[0] == d[4];} %}
| AS _ "!=" _ AS {% function(d) {return d[0] != d[4];} %}
# 数字或数字函数
N -> float {% id %}
| ("ceiling"|"CEILING") _ P {% function(d) {return Math.ceil(d[2]); } %} # 进一取整
| ("floor"|"FLOOR") _ P {% function(d) {return Math.floor(d[2]); } %} # 舍一取整
| ("round"|"ROUND") _ P {% function(d) {return Math.round(d[2]); } %} # 四舍五入
| "${" VAR ("." VAR):* "}" {% function(d) {
let l = d[2].length;
let v = "${" + d[1];
for (let i = 0; i < l; i++) {
v += "." + d[2][i][1];
}
return v + "}";
} %} # 变量,支持.循环的多级变量
# 变量,可以是中文,英文,数字,下划线,中横线的组合
VAR -> [\u4e00-\u9fa5a-zA-Z\d_\-]:+ {% function(d) {return d[0].join(""); } %}
# 有小数点的数字
float -> int "." unsigned_int {% function(d) {return parseFloat(d[0] + d[1] + d[2])} %}
| int {% function(d) {return parseInt(d[0])} %}
# 无符号整型(正数)
unsigned_int -> [0-9]:+ {% function(d) {return d[0].join(""); } %}
# 空白,比如空格和换行。这里重要的是后处理器是一个null返回函数。这是一个内存效率技巧。
_ -> [\s]:* {% function(d) {return null; } %}
# 整型(包含负数)
int -> ("-"|"+"):? [0-9]:+ {%
function(d) {
if (d[0]) {
return parseInt(d[0][0]+d[1].join(""));
} else {
return parseInt(d[1].join(""));
}
}
%}
使用时的代码示例:
CASE(${数量} > 0, ${金额}) * 1.5
${明细.计划数} * ${单价}