go/parser仅支持go语法,无法解析含自定义变量、操作符或省略括号的dsl表达式;需自行实现词法分析器,关键在于准确消耗字节且不回退,如用utf8.decoderuneinstring更新pos而非strings.hasprefix误判。

为什么不能直接用 go/parser 解析自定义表达式
因为 go/parser 只认 Go 语法,你的 "a + b * 2 > 10 && !flag" 如果含自定义变量、操作符(如 ?? 空合并)、或省略括号的优先级规则,它会直接报 syntax error: unexpected ...。你不是在写 Go 表达式,而是在实现一个微型领域语言(DSL)求值器——得从词法分析开始自己控场。
lex.Next() 怎么写才不漏 token、不错位
关键不是“识别出什么”,而是“消耗掉多少字节且不回退”。常见错误是用 strings.HasPrefix 匹配关键字后忘了跳过对应长度,导致下一个 lex.Next() 从中间开始读,后续全乱。正确做法是用 strings.IndexFunc 或手动遍历,记录当前 lex.pos 和结束位置:
func (l *lexer) nextToken() token {
l.skipWhitespace()
start := l.pos
if l.pos >= len(l.input) {
return token{typ: eof}
}
r, width := utf8.DecodeRuneInString(l.input[l.pos:])
l.pos += width
switch {
case unicode.IsLetter(r):
return l.scanIdentifier(start)
case unicode.IsDigit(r):
return l.scanNumber(start)
case r == '"':
return l.scanString(start)
case r == '=' && l.pos =,
- 所有
scanXxx方法必须更新l.pos到该 token 结束之后,不能依赖外部推进 - 单字符运算符(
+,-,!)可直接返回;双字符(==,&&)必须预读并确认,否则会把&&拆成两个& - 数字扫描要区分整数/浮点/科学计数法,否则
"1e2"被截成"1"后剩"e2"报错
如何让 ParseExpr() 正确处理左结合与优先级
递归下降解析器里,优先级靠函数嵌套深度体现,左结合靠循环而非递归。比如加减法必须写成:left := ParseTerm(); for next is '+' or '-' { left = &BinaryExpr{Op: tok, Left: left, Right: ParseTerm()} }。如果写成 ParseExpr → ParseExpr '+' ParseTerm,就是右结合,"1-2-3" 会算成 1-(2-3)=2,而非预期的 (1-2)-3=-4。
- 优先级分层建议:原子(标识符/数字/括号)→ 一元(
!,-)→ 乘除模 → 加减 → 关系(==,)→ 逻辑与(<code>&&)→ 逻辑或(||) -
ParsePrimary遇到'('必须调用ParseExpr()并匹配')',否则括号无法嵌套 - 不要在解析时求值,先构造 AST 节点(如
&BinaryExpr{Left: ..., Op: "+", Right: ...}),等整个树建完再Eval()
Eval() 遇到未定义变量或类型不匹配怎么反馈
硬 panic 不行,用户字符串不可信。应该统一返回 value, err,且 err 必须带位置信息。例如变量 a 未定义,不能只报 "undefined variable a",而要返回 &EvalError{Msg: "undefined variable", Var: "a", Pos: 3},方便上层高亮错误位置。
- 二元运算前务必检查左右操作数类型:
if l.Kind != right.Kind { return nil, fmt.Errorf("type mismatch: %s op %s", l.Kind, right.Kind) } - 短路逻辑(
&&,||)必须先Evaluate(left),若已能确定结果(如false && X),直接跳过X的求值,否则可能触发无意义副作用(比如X是函数调用) - 空合并
a ?? b要明确定义“空”:对字符串是"",对数字是0还是nil?这个语义必须在文档写死,代码里做显式判断,别依赖 interface{} 的零值模糊处理
最易被忽略的是错误恢复能力——词法分析阶段的非法字符、解析阶段的括号不匹配、运行时的除零,三者错误类型不同,但对外暴露的 error 接口应一致,否则上层无法统一处理。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











