数据系统常常需要在某个流节点执行简单的数据处理操作,例如单位的转换。
假设传入的值为input,在这一节点,输出的结果定义为 input * 8 / 1024
, 为了方便运维同事修改,这个算式并未固化在代码中,而是使用string的方式定义在配置文件中。
如:
rules:
rule1: "input * 8 / 1024"
要在go中动态的载入string
的算式并计算,一种简单的方式是使用语法解析树。生成这个算式对应的解析树,然后执行它。
import (
"fmt"
"go/parser"
"go/token"
"testing"
)
func TestMyAst(t *testing.T) {
expression := "input*8/1024"
// Create a file set for Go syntax tree
fset := token.NewFileSet()
// Parse the expression
expr, err := parser.ParseExprFrom(fset, "", expression, 0)
if err != nil {
fmt.Printf("Failed to parse the expression: %v\n", err)
return
}
// Create the context environment for evaluation
data := 169661
context := map[string]int64{
"input": int64(data),
}
// Evaluate the expression
result := eval(expr, context)
logrus.Infof("result:%v %v", data, result)
}
此时,将传入参数放在context
中,然后调用eval执行。
parser.ParseExprFrom
的结果可以缓存下来,因为它只和算式有关。以提高性能。
eval
的代码如下,通过完善eval
,可以利用解析树执行更复杂的功能。下面的eval函数可以方便的执行二元运算,满足 input * 8 / 1024
的计算需求。
func eval(expr ast.Expr, context map[string]int64) int64 {
switch e := expr.(type) {
case *ast.Ident:
// Handle identifiers (variables)
value, ok := context[e.Name]
if !ok {
fmt.Printf("Undefined variable: %s\n", e.Name)
return 0
}
return value
case *ast.BasicLit:
// Handle basic literals (constants)
value, _ := strconv.ParseInt(e.Value, 0, 64)
return value
case *ast.BinaryExpr:
// Handle binary expressions
left := eval(e.X, context)
right := eval(e.Y, context)
switch e.Op {
case token.ADD:
return left + right
case token.SUB:
return left - right
case token.MUL:
return left * right
case token.QUO:
return left / right
default:
fmt.Printf("Unsupported operator: %s\n", e.Op)
return 0
}
default:
fmt.Printf("Unsupported expression type: %T\n", e)
return 0
}
}