go: 如何高效的执行字符串算术表达式 数据系统常常需要在某个流节点执行简单的数据处理操作,例如单位的转换。 假设传入的值为input,在这一节点,输出的结果定义为 `input * 8 / 1024`, 为了方便运维同事修改,这个算式并未固化在代码中,而是使用string的方式定义在配置文件中。 如: ```yaml rules: rule1: "input * 8 / 1024" ``` 要在go中动态的载入`string`的算式并计算,一种简单的方式是使用语法解析树。生成这个算式对应的解析树,然后执行它。 ```go 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` 的计算需求。 ```go 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 } } ``` 来自 大脸猪 写于 2023-07-07 16:36 -- 更新于2023-07-07 16:50 -- 1 条评论