如何使用Golang实现简单表达式计算_Golang基本语法操作实践

Go标准库无eval,用go/parser+go/constant可安全求值纯数字四则表达式,但不支持变量、函数或逻辑运算;需前置校验输入合法性,支持变量时应改用antonmedv/expr等专用引擎。

Go 语言标准库没有内置表达式求值器,eval 类功能需手动实现或借助第三方包;直接用 go/parser + go/constant 可安全解析简单算术表达式,但不支持变量、函数调用或逻辑运算。

go/parsergo/constant 解析并计算纯数字表达式

这是最轻量、最安全的方案,适用于形如 "2 + 3 * 4""(10 - 2) / 2" 的整数/浮点数四则运算。它绕过字符串拼接执行,避免代码注入风险。

  • 只支持常量表达式(不能含变量、标识符、函数调用)
  • 需将输入包裹成合法 Go 表达式语句,例如加前缀 "const _ = "
  • go/parser.ParseExpr 解析 AST,再用 go/constant.Int64ValFloat64Val 提取结果
  • 整数除法会自动截断,若需浮点结果,确保至少一个操作数带小数点(如 "6.0 / 4"
package main

import ( "fmt" "go/ast" "go/parser" "go/token" "go/constant" )

func evalExpr(s string) (float64, error) { expr, err := parser.ParseExpr("const _ = " + s) if err != nil { return 0, err }

// 确保是二元运算或字面量
tv, err := ast.EvalExpr(token.NewFileSet(), nil, expr)
if err != nil {
    return 0, err
}

switch tv.Kind() {
case constant.Int:
    return constant.Int64Val(tv.Value), nil
case constant.Float:
    return constant.Float64Val(tv.Value), nil
default:
    return 0, fmt.Errorf("unsupported constant kind: %v", tv.Kind())
}

}

func main() { result, _ := evalExpr("2 + 3 * 4") fmt.Println(result) // 14 }

遇到 invalid operationundefined: xxx 错误怎么办

这类错误几乎都源于输入不是合法 Go 常量表达式:

  • undefined: x → 表达式里写了变量名(如 "x + 1"),而 go/parser 不做变量绑定
  • invalid operation: operator + not defined on string → 输入含未加引号的字符串字面量(如 "hello + world"),应只传数字和运算符
  • syntax error: unexpected newline → 字符串含换行或注释,需提前清理(strings.TrimSpace + 去掉 ///* */
  • 空字符串或只有空格 → ParseExpr 直接返回语法错误,建议前置校验 len(strings.TrimSpace(s)) == 0

需要支持变量时,别硬改 go/parser,换用 antonmedv/expr

go/parser 天然不支持运行时变量代入,强行在 AST 上做符号表替换极易出错且破坏类型安全。更现实的做法是引入轻量表达式引擎:

  • github.com/antonmedv/expr 支持变量、布尔逻辑、比较、基础函数(len, round),语法接近 Go 但更宽松
  • 它仍不执行任意代码(无 exec、无反射调用),安全性可控
  • 性能比 go/parser 略低,但对千次/秒以下的计算场景无感
  • 注意:默认禁止访问结构体字段(如 user.Name),需显式启用 expr.Env 并传入允许的字段名列表
import (
    "fmt"
    "github.com/antonmedv/expr"
)

func main() { env := map[string]interface{}{"a": 5, "b": 3} code := "a * b + 2" program, := expr.Compile(code) output, := expr.Run(program, env) fmt.Println(output) // 17 }

真正麻烦的不是写个计算器,而是决定「哪些东西必须禁止」:用户输入是否可能含恶意构造(如超深递归、超长数字、科学计数法溢出)?是否要限制执行时间?这些边界问题比语法解析本身更消耗精力。