go不支持eval因安全风险、性能差且不必要;应构建递归下降解析器,分层处理优先级、单独处理括号、统一解析叶子节点;eval需支持路径访问与nil安全;性能优化重在词法分析、ast复用和表达式缓存。

为什么不能直接用 eval 类函数解析表达式
Go 没有内置的 eval,强行用 go/parser + go/ast 编译执行不仅慢,还存在安全风险(比如用户输入恶意代码)、内存开销大、无法控制执行超时。规则引擎里常见的是类似 "age > 18 && (city == 'beijing' || score >= 90)" 这种结构化布尔表达式,不是任意 Go 代码——没必要走 AST 编译路径。
递归下降解析器怎么写才不崩
核心是把字符串表达式构造成一棵二叉表达式树(AST),再递归求值。关键不在“递归”本身,而在如何避免左递归导致栈溢出、括号嵌套过深时 panic、以及类型不一致引发的 panic。
- 优先级必须显式分层:用多个函数分别处理
OR、AND、COMPARISON、PRIMARY,每层只 consume 当前优先级的运算符 - 括号必须用
parseGroup()单独处理,遇到'('就递归调用顶层解析函数,匹配到')'后立即返回,避免深度累积 - 所有叶子节点(如变量名、数字、字符串字面量)统一走
parsePrimary(),它返回interface{},但后续比较前必须做类型断言或转换,否则1 == "1"这类会静默失败
func (e *Expr) Eval(ctx map[string]interface{}) (bool, error) 怎么设计上下文传参
规则引擎的变量来源往往是动态的:HTTP 请求参数、数据库查出的结构体字段、缓存里的 JSON 对象。硬编码 ctx["user_age"] 不现实,得支持点号路径("user.profile.age")和数组索引("orders[0].amount")。
- 不要在
Eval()里直接做反射取值——性能差且 panic 难捕获;提前在构建 AST 阶段就把变量路径解析成[]string或[]interface{},运行时用简单循环查 map/slice - 对缺失字段默认返回
nil,并在比较操作中约定:nil == nil为 true,nil == 其他值为 false,避免user.name == "admin"因user为空而 panic - 如果 ctx 是
map[string]interface{},注意它不支持直接取ctx["user"]["profile"]["age"],得用getNested(ctx, []string{"user","profile","age"})工具函数
性能瓶颈常卡在哪儿
实测发现,90% 的耗时不在表达式逻辑计算,而在字符串切分、正则匹配 token、反复构造临时 slice 和 interface{}。尤其当单次请求要 eval 数千条规则时,GC 压力陡增。
- 词法分析阶段别用
regexp——用状态机手动 scan,按字符遍历一次完成 token 切分,速度提升 3–5 倍 - AST 节点复用:把
BinaryOpNode、VarRefNode定义为 struct 而非指针,避免逃逸;解析时用预分配的[]tokenslice,而非 append - 缓存已编译的表达式:相同字符串表达式(如
"status == 'active'")应复用同一棵 AST,用sync.Map存string → *Expr,避免重复 parse
真正难的不是写出能跑的递归求值器,而是让每次 Evaluate() 的内存分配趋近于零、panic 可控、变量路径解析不出错。这些细节不压测根本暴露不出来。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











