今天咱們介紹一個比較好玩的庫govaluate
。govaluate
與 JavaScript 中的eval
功能相似,用於計算任意表達式的值。此類功能函數在 JavaScript/Python 等動態語言中比較常見。govaluate
讓 Go 這個編譯型語言也有了這個能力!git
先安裝:github
$ go get github.com/Knetic/govaluate
後使用:數組
package mainimport ( "fmt" "log" "github.com/Knetic/govaluate")func main() { expr, err := govaluate.NewEvaluableExpression("10 > 0") if err != nil { log.Fatal("syntax error:", err) } result, err := expr.Evaluate(nil) if err != nil { log.Fatal("evaluate error:", err) } fmt.Println(result)}
使用govaluate
計算表達式只須要兩步:服務器
NewEvaluableExpression()
將表達式轉爲一個表達式對象;Evaluate
方法,傳入參數,返回表達式的值。上面演示了一個很簡單的例子,咱們使用govaluate
計算10 > 0
的值,該表達式不須要參數,故傳給Evaluate()
方法nil
值。固然,這個例子並不實用,顯然咱們直接在代碼中計算10 > 0
更簡單。但問題是,有些時候咱們並不知道須要計算的表達式的全部信息,甚至咱們都不知道表達式的結構。這時govaluate
的做用就體現出來了。微信
govaluate
支持在表達式中使用參數,調用表達式對象的Evaluate()
方法時經過map[string]interface{}
類型將參數傳入計算。其中map
的鍵爲參數名,值爲參數值。例如:函數
func main() { expr, _ := govaluate.NewEvaluableExpression("foo > 0") parameters := make(map[string]interface{}) parameters["foo"] = -1 result, _ := expr.Evaluate(parameters) fmt.Println(result) expr, _ = govaluate.NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90") parameters = make(map[string]interface{}) parameters["requests_made"] = 100 parameters["requests_succeeded"] = 80 result, _ = expr.Evaluate(parameters) fmt.Println(result) expr, _ = govaluate.NewEvaluableExpression("(mem_used / total_mem) * 100") parameters = make(map[string]interface{}) parameters["total_mem"] = 1024 parameters["mem_used"] = 512 result, _ = expr.Evaluate(parameters) fmt.Println(result)}
第一個表達式中,咱們想要計算foo > 0
的結果,在傳入參數中將foo
設置爲 -1,最終輸出false
。學習
第二個表達式中,咱們想要計算(requests_made * requests_succeeded / 100) >= 90
的值,在參數中設置requests_made
爲 100,requests_succeeded
爲 80,結果爲true
。this
上面兩個表達式都返回bool
結果,第三個表達式返回一個浮點數。(mem_used / total_mem) * 100
根據傳入的總內存total_mem
和當前使用內存mem_used
,返回內存佔用百分比,結果爲 50。lua
使用govaluate
與直接編寫 Go 代碼不一樣,在 Go 代碼中標識符中不能出現-
、+
、$
等符號。govaluate
能夠經過轉義使用這些符號。有兩種轉義方式:spa
[
和]
包裹起來,例如[response-time]
;\
將緊接着下一個的字符轉義。例如:
func main() { expr, _ := govaluate.NewEvaluableExpression("[response-time] < 100") parameters := make(map[string]interface{}) parameters["response-time"] = 80 result, _ := expr.Evaluate(parameters) fmt.Println(result) expr, _ = govaluate.NewEvaluableExpression("response\\-time < 100") parameters = make(map[string]interface{}) parameters["response-time"] = 80 result, _ = expr.Evaluate(parameters) fmt.Println(result)}
注意一點,由於在字符串中\
自己就是須要轉義的,因此在第二個表達式中要使用\\
。或者可使用
`response\-time` < 100
使用帶參數的表達式,咱們能夠實現一個表達式的一次「編譯」,屢次運行。只須要使用編譯返回的表達式對象便可,可屢次調用其Evaluate()
方法:
func main() { expr, _ := govaluate.NewEvaluableExpression("a + b") parameters := make(map[string]interface{}) parameters["a"] = 1 parameters["b"] = 2 result, _ := expr.Evaluate(parameters) fmt.Println(result) parameters = make(map[string]interface{}) parameters["a"] = 10 parameters["b"] = 20 result, _ = expr.Evaluate(parameters) fmt.Println(result)}
第一次運行,傳入參數a = 1, b = 2
獲得結果 3;第二次運行,傳入參數a = 10, b = 20
獲得結果 30。
若是僅僅能進行常規的算數和邏輯運算,govaluate
的功能會大打折扣。govaluate
提供了自定義函數的功能。全部自定義函數須要先定義好,存入一個map[string]govaluate.ExpressionFunction
變量中,而後調用govaluate.NewEvaluableExpressionWithFunctions()
生成表達式,此表達式中就可使用這些函數了。自定義函數類型爲func (args ...interface{}) (interface{}, error)
,若是函數返回錯誤,則這個表達式求值返回錯誤。
func main() { functions := map[string]govaluate.ExpressionFunction{ "strlen": func(args ...interface{}) (interface{}, error) { length := len(args[0].(string)) return length, nil }, } exprString := "strlen('teststring')" expr, _ := govaluate.NewEvaluableExpressionWithFunctions(exprString, functions) result, _ := expr.Evaluate(nil) fmt.Println(result)}
上面例子中,咱們定義一個函數strlen
計算第一個參數的字符串長度。表達式strlen('teststring')
調用strlen
函數返回字符串teststring
的長度。
函數能夠接受任意數量的參數,並且能夠處理嵌套函數調用的問題。因此能夠寫出相似下面這種複雜的表達式:
sqrt(x1 ** y1, x2 ** y2)max(someValue, abs(anotherValue), 10 * lastValue)
在 Go 語言中,訪問器(Accessors
)就是經過.
操做訪問結構中的字段。若是傳入的參數中有結構體類型,govaluate
也支持使用.
訪問其內部字段或調用它們的方法:
type User struct { FirstName string LastName string Age int}func (u User) Fullname() string { return u.FirstName + " " + u.LastName}func main() { u := User{FirstName: "li", LastName: "dajun", Age: 18} parameters := make(map[string]interface{}) parameters["u"] = u expr, _ := govaluate.NewEvaluableExpression("u.Fullname()") result, _ := expr.Evaluate(parameters) fmt.Println("user", result) expr, _ = govaluate.NewEvaluableExpression("u.Age > 18") result, _ = expr.Evaluate(parameters) fmt.Println("age > 18?", result)}
在上面代碼中,咱們定義了一個User
結構,併爲它編寫了一個Fullname()
方法。第一個表達式中,咱們調用u.Fullname()
返回全名,第二個表達式比較年齡是否大於 18。
須要注意的一點是,咱們不能使用foo.SomeMap['key']
的方式訪問map
的值。因爲訪問器涉及到不少反射,因此它通常比直接使用參數慢 4 倍左右。若是能使用參數的形式,儘可能使用參數。在上面的例子中,咱們能夠直接調用u.Fullname()
,將結果做爲參數傳給表達式求值。涉及到複雜的計算能夠經過自定義函數來解決。咱們還能夠實現govaluate.Parameter
接口,對於表達式中使用的未知參數,govaluate
會自動調用其Get()
方法獲取:
// src/github.com/Knetic/govaluate/parameters.gotype Parameters interface { Get(name string) (interface{}, error)}
例如,咱們可讓User
實現Parameter
接口:
type User struct { FirstName string LastName string Age int}func (u User) Get(name string) (interface{}, error) { if name == "FullName" { return u.FirstName + " " + u.LastName, nil } return nil, errors.New("unsupported field " + name)}func main() { u := User{FirstName: "li", LastName: "dajun", Age: 18} expr, _ := govaluate.NewEvaluableExpression("FullName") result, _ := expr.Eval(u) fmt.Println("user", result)}
表達式對象實際上有兩個方法,一個是咱們前面用的Evaluate()
,這個方法接受一個map[string]interface{}
參數。另外一個就是咱們在這個例子中使用的Eval()
方法,該方法接受一個Parameter
接口。實際上,在Evaluate()
實現內部也是調用的Eval()
方法:
// src/github.com/Knetic/govaluate/EvaluableExpression.gofunc (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) { if parameters == nil { return this.Eval(nil) } return this.Eval(MapParameters(parameters))}
在表達式計算時,未知的參數都須要調用Parameter
的Get()
方法獲取。上面的例子中咱們直接使用FullName
就能夠調用u.Get()
方法返回全名。
govaluate
支持的操做和類型與 Go 語言有些不一樣。一方面govaluate
中的類型和操做不如 Go 豐富,另外一方面govaluate
也對一些操做進行了擴展。
算數、比較和邏輯運算:
+
-
/
*
&
|
^
**
%
>>
<<
:加減乘除,按位與,按位或,異或,乘方,取模,左移和右移;>
>=
<
<=
==
!=
=~
!~
:=~
爲正則匹配,!~
爲正則不匹配;||
&&
:邏輯或和邏輯與。常量:
govaluate
中將數字都做爲 64 位浮點數處理;govaluate
中,字符串用單引號'
;govaluate
會嘗試自動解析字符串是不是日期,只支持 RFC333九、ISO8601等有限的格式;true
、false
。其餘:
()
中,每一個元素之間用,
分隔,能夠支持任意的元素類型,如(1, 2, 'foo')
。實際上在govaluate
中數組是用[]interface{}
來表示的;? :
。在下面代碼中,govaluate
會先將2014-01-02
和2014-01-01 23:59:59
轉爲time.Time
類型,而後再比較大小:
func main() { expr, _ := govaluate.NewEvaluableExpression("'2014-01-02' > '2014-01-01 23:59:59'") result, _ := expr.Evaluate(nil) fmt.Println(result)}
在上面的例子中,咱們刻意忽略了錯誤處理。實際上,govaluate
在建立表達式對象和表達式求值這兩個操做中均可能產生錯誤。在生成表達式對象時,若是表達式有語法錯誤,則返回錯誤。表達式求值,若是傳入的參數不合法,或者某些參數缺失,或者訪問結構體中不存在的字段都會報錯。
func main() { exprString := `>>>` expr, err := govaluate.NewEvaluableExpression(exprString) if err != nil { log.Fatal("syntax error:", err) } result, err := expr.Evaluate(nil) if err != nil { log.Fatal("evaluate error:", err) } fmt.Println(result)}
咱們能夠依次修改表達式字符串,驗證各類錯誤,首先是>>>
:
2020/04/01 22:31:59 syntax error:Invalid token: '>>>'
而後咱們將其修改成foo > 0
,可是咱們沒有傳入參數foo
,執行失敗:
2020/04/01 22:33:07 evaluate error:No parameter 'foo' found.
其餘錯誤能夠自行驗證。
govaluate
雖然支持的操做和類型有限,也能實現比較有意思的功能。例如,能夠寫一個 Web 服務,由用戶本身編寫表達式,設置參數,服務器算出結果。
你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄
我
-
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~