dsh-expr-engine
面向 DeepSeek Harness 生态的表达式引擎插件(零运行时依赖):递归下降解析 + 运算符优先级 + Excel 公式子集函数库(40+ 函数)。可对单表达式求值、批量数据逐行计算列,全程内存内操作、不碰磁盘。
功能说明
eval_expr | 求值单个表达式(支持 vars 变量表) | {ok, value, type};错误抛出并带字符位置 |
validate_expr | 校验语法(解析不求值) | {ok, functions, variables, nodes} 或 {ok:false, error, position} |
eval_column | 对数据行数组逐行求值(计算列) | {total, ok_count, error_count, values, errors};单行失败不影响其他行 |
语法与优先级(从高到低,Excel 口径):
- 原子:数字(
1.5、1e3)、字符串("…"/'…')、TRUE/FALSE、变量(标识符 / [列 名] 方括号引用)、函数调用、括号
- 一元
+/-(优先级高于 ^:-3^2 = 9)
% 百分比后缀(50% = 0.5;取模请用 MOD 函数)
^ 幂(左结合:2^3^2 = 64,与数学惯例相反,与 Excel 一致)
* /
+ -
& 文本连接
- 比较
= <> < <= > >=(同时接受 ==/!=)
类型语义(Excel 口径):数字字符串自动转数值("1"+2=3);文本比较不区分大小写("a"="A" 为真);布尔参与算术(TRUE+1=2);空值算术按 0、文本按空串;数字序列化保留 15 位有效数字(规避二进制浮点尾差显示)。
函数库(40+,名称不区分大小写):
- 聚合:
SUM PRODUCT AVERAGE/AVG MIN MAX MEDIAN COUNT COUNTA(参数中的数组自动展开)
- 标量数学:
ABS SIGN SQRT POWER EXP LN LOG10 INT MOD(符号随除数)ROUND(远离零舍入,十进制修正尾差)ROUNDUP ROUNDDOWN FLOOR CEILING PI
- 逻辑:
IF(2~3 参,惰性求值)IFERROR(惰性)AND OR NOT TRUE FALSE ISNUMBER ISTEXT ISBLANK
- 文本:
CONCAT LEN UPPER LOWER TRIM(压缩内部连续空格)LEFT RIGHT MID(1 起始)SUBSTITUTE FIND(区分大小写)SEARCH(不区分)REPT
安装
dsh plugin --profile web add dsh-expr-engine
使用说明
1. eval_expr —— 求值表达式
expr | string | 必填 | 表达式,允许 Excel 风格前导 = |
vars | object | 无 | 变量表(值为 number/string/boolean/数组;数组供聚合函数展开) |
eval_expr { expr: "=ROUND(AVERAGE(分数)*0.9, 1)", vars: { 分数: [80, 92, 75] } }
→ { "ok": true, "value": 74.1, "type": "number" }
2. validate_expr —— 校验表达式
validate_expr { expr: "IF(总额>1000, 总额*0.95, 总额)" }
→ { "ok": true, "functions": ["IF"], "variables": ["总额"], "nodes": 7 }
语法错误返回 {"ok": false, "error": "…", "position": 5}(position 为 0 起始字符位置);未知函数也判不合法。
3. eval_column —— 批量计算列
expr | string | 必填 | 每行求值的表达式,标识符解析为列名(大小写不敏感;含空格列名用 [列名]) |
rows | array | 必填 | 对象数组,每行键即可用列名(上限 10000 行) |
vars | object | 无 | 全局变量(行内同名列优先) |
eval_column { expr: 'IF(营收>0,(营收-成本)/营收,"")', rows: [{营收:100,成本:60},{营收:0,成本:30}] }
→ { total: 2, ok_count: 2, error_count: 0, values: [0.4, ""] }
典型场景
- 模型心算兜底:模型容易算错
15 + 27 * sqrt(9) 这类式子,交给本工具确定性求值
- 数据表加计算列:JSON 数据批量算毛利率/同环比/加权分,单行脏数据不拖垮整批
- 表达式预检:把用户或模型写的公式先
validate_expr,拿到语法错误位置再修复
- 评分/风控规则:
IF(AND(分>=60,逾期=0),"通过","复核") 这类业务规则表达式化
- 报表口径试算:vars 换参数重跑同一表达式,快速对比口径差异
安全边界
- 纯内存操作:只做字符串/数值运算,不读盘、不写盘、不访问网络
- 防御上限明确:表达式长度 10000 字符、词元 5000、嵌套深度 200、行数 10000,超限报错不静默
- 无 eval/new Function:解析器为手写递归下降,不注入宿主能力,表达式无法触达文件系统或进程
- 错误显式化:除零/类型错/未知变量/未知函数均抛错并带 Excel 错误码(#DIV/0!、#VALUE!、#NUM!)与字符位置
- 确定性:无随机、无时间依赖,同输入必同输出
版本说明
v0.1.0(2026-10-21)
- 首个版本:
eval_expr / validate_expr / eval_column 三工具
- 手写递归下降解析器,Excel 口径优先级(负号高于 ^、^ 左结合、% 后缀)与前导
= 剥离
- Excel 类型语义:文本数字自动转换、比较不区分大小写、15 位有效数字序列化、ROUND 系列十进制尾差修正
- 40+ 函数库(聚合/标量数学/逻辑惰性求值/文本),数组参数自动展开
- 153 项结构断言全绿(含优先级、边界、防御、错误位置、批量列)
RoadMap
- v0.2:
IFS/SWITCH/XLOOKUP 函数;数组字面量 {1,2,3};日期函数(TODAY 除外——保持确定性,改为参数传基准日期)
- v0.3:
eval_table(多表达式一次建多列);表达式格式化/美化(format_expr);与 sql-query 联动(SELECT 内嵌表达式)
- v0.4:单元感知(类似 Excel A1 引用的虚拟网格);性能:表达式编译缓存(AST 复用,万行加速)
其他插件集成说明
- plugin-calculator(社区):带计算过程记录的计算器;本插件无状态、纯求值,两者互补可同装
- dsh-sql-query:SQL 查询面向数据集(过滤/分组/聚合);本插件面向单表达式与逐行计算列——SQL 算完的行可再交给
eval_column 做衍生列
- dsh-xlsx-lite / dsh-pivot-join:读表/透视产出 JSON 行后,用本插件批量补计算列再回写
- dsh-data-clean(四期 D10):清洗流水线中的规范化规则(如
IF(LEN(手机号)=11,手机号,""))可用本插件表达
- dsh-stats-toolkit(四期 D9):统计口径(分位数/移动平均)由 stats 承担,本插件负责行级四则与条件
- dsh-fulltext-index:检索得分公式(加权求和)可先 validate 再 eval_column 批量算分
FAQ
Q: 支持 Excel 的 A1 引用(A1:B10)吗?
A: 不支持。本插件是内存表达式引擎,数据通过 vars/rows 传入,列名直接引用;网格引用在 RoadMap v0.4。
Q: 为什么 -3^2 是 9 而 2^3^2 是 64?
A: 均为 Excel 真实行为:一元负号优先级高于 ^,^ 是左结合。按数学惯例使用时建议加括号。
Q: % 能当取模用吗?
A: 不能。Excel 中 % 只有百分比后缀语义,取模用 MOD(a,b)(注意符号随除数,与 JS 的 % 不同)。
Q: 字符串比较区分大小写吗?
A: 比较运算符不区分(Excel 口径);需要区分时用 FIND(区分)而非 SEARCH(不区分)。
Q: 空单元格参与运算怎么处理?
A: 按单元格语义:算术按 0(空+1=1)、连接按空串、逻辑按 FALSE;ISBLANK 可显式判空。
Q: 单行计算失败会怎样?
A: eval_column 逐行隔离,失败行值为 null 并记入 errors(行号 + 原因),其余行正常返回;eval_expr 单表达式则直接抛错。
Q: 表达式能执行任意代码吗?
A: 不能。解析器手写、无 eval、无宿主能力注入,表达式只能触达数字/文本/布尔/变量/函数库。
许可证
MIT