不能直接用eval或第三方ast解析器,因go无内置eval,硬套python/js会引入非安全执行环境或重量级依赖;而go/parser专为go源码设计,不支持sin(2+3)*pi等数学表达式,且将pi判为未定义标识符。

为什么不能直接用 eval 或第三方 AST 解析器?
Go 没有内置的 eval,硬套 Python/JS 那套会引入非安全执行环境或重量级依赖;而直接上完整 AST 解析器(比如用 go/parser)又过度——它专为 Go 源码设计,不认 sin(2+3)*pi 这类数学表达式,还会把 pi 当未定义标识符报错。
真正要的是轻量、可控、可扩展的「数学表达式解析器」:支持四则、括号、函数调用、常量,且能注入自定义变量和函数。
推荐路径:手写递归下降解析器 + 简单词法分析。不依赖外部包,核心逻辑 200 行内可闭环。
Lex 阶段必须识别哪些 token?
数学表达式词法比编程语言简单,但容易漏掉边界情况。关键 token 类型包括:
-
NUMBER:支持整数、小数、科学计数法(如1e-3),注意.5是合法的,不能只匹配\d+\.\d* -
IDENT:变量名或函数名,需区分保留字(如pi、e)和用户变量(如x、theta) -
LPAREN/RPAREN、PLUS/MINUS、MUL/DIV/POW(^或**) -
COMMA:用于多参函数,如max(a,b)
常见坑:- 既是减号又是负号,需结合上下文判断(前一个 token 是运算符或起始位置 → 负号);^ 在部分场景被当异或,建议统一用 ** 表示幂,避免歧义。
如何实现带优先级的递归下降解析?
不用构造完整 AST,直接在解析过程中计算(即「递归下降 + 直接求值」)。核心是按运算符优先级分层函数:
-
parseExpr():处理+/-(最低优先级) -
parseTerm():处理*// -
parseFactor():处理**(右结合)、正负号、括号、函数调用 -
parsePrimary():处理数字、标识符、(...)
示例片段(简化):
func (p *parser) parseExpr() float64 {
left := p.parseTerm()
for p.peek().typ == PLUS || p.peek().typ == MINUS {
op := p.next()
right := p.parseTerm()
if op.typ == PLUS {
left += right
} else {
left -= right
}
}
return left
}
注意:** 必须右结合,所以 parseFactor 内部要循环向右展开,不能简单套用左结合模式。
怎么安全注入变量和函数?
解析器本身不硬编码 pi 或 sin,而是通过回调接口:
- 变量查表:传入
map[string]float64,如vars["x"] = 2.5 - 函数调用:定义类型
type Func func([]float64) float64,注册时映射"sin": math.Sin、"log": math.Log,注意多参函数(如pow)需适配参数数量
关键约束:所有函数必须返回 float64,输入参数也强制转为 float64;遇到未注册函数或变量,应明确返回错误(如 "unknown function 'foo'"),而不是 panic。
容易被忽略的一点:浮点精度误差在链式计算中会累积,如果业务对精度敏感(比如金融公式),需在最终结果做 math.Round(x*1e10) / 1e10 类似截断,但要清楚这会掩盖真实误差来源。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











