introduction
This article is receivedGolang interview classic explanation
The [Go Tool Library] B website’s new generation golang rules engine gengine reveals. The basic usage and the details of the author are basically mentioned. If you are concerned, you can check out the design documents and the author’s comparison.gopher_lua
Comparison
This article mainly contains several cases and usages of the author
Part1 Basic use
package main import ( "fmt" "/bilibili/gengine/builder" "/bilibili/gengine/context" "/bilibili/gengine/engine" "/sirupsen/logrus" "time" ) func main() { TestMulti() } type User struct { Name string Age int64 Male bool } func (u *User) GetNum(i int64) int64 { return i } func (u *User) Print(s string) { (s) } func (u *User) Say() { ("hello world") } // Define rulesconst rule1 = ` rule "name test" "i can" salience 0 begin if 7 == (7){ = (89767) + 10000000 ("6666") }else{ = "yyyy" } end ` func TestMulti() { user := &User{ Name: "Calo", Age: 0, Male: true, } dataContext := () //Inject the initialized structure ("User", user) //init rule engine ruleBuilder := (dataContext) start1 := () //Build rules err := (rule1) //string(bs) end1 := () ("rules num:%d, load rules cost time:%d", len(), (start1).Milliseconds()) if err != nil { ("err:%s ", err) } else { eng := () start := ().UnixNano() //Execute the rules err := (ruleBuilder, true) println() end := ().UnixNano() if err != nil { ("execute rule error: %v", err) } ("execute rule cost %d ns", end-start) ("=%d,Name=%s,Male=%t", , , ) } }
INFO[0000] rules num:1, load rules cost time:5
6666
10089767
INFO[0000] execute rule cost 97000 ns
INFO[0000] =10089767,Name=Calo,Male=true
Part2 @name
Use @name in the rule body, which refers to the current rule name. When the rule is executed, @name will be parsed into a rule name string (name perception within the rule)
package main import ( "fmt" "/bilibili/gengine/builder" "/bilibili/gengine/context" "/bilibili/gengine/engine" "time" ) func PrintName(name string) { (name) } /* * use '@name',you can get rule name in rule content */ const atNameRule = ` rule "Test rule name 1" "rule desc" begin va = @name PrintName(va) PrintName(@name) end rule "rule name" "rule desc" begin va = @name PrintName(va) PrintName(@name) end ` func exec() { dataContext := () ("PrintName", PrintName) //init rule engine ruleBuilder := (dataContext) //resolve rules from string start1 := ().UnixNano() err := (atNameRule) end1 := ().UnixNano() println(("rules num:%d, load rules cost time:%d ns", len(), end1-start1)) if err != nil { panic(err) } eng := () start := ().UnixNano() // true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule err = (ruleBuilder, true) end := ().UnixNano() if err != nil { panic(err) } println(("execute rule cost %d ns", end-start)) } func main() { exec() }
rules num:2, load rules cost time:2820000 ns
Test rule name 1
Test rule name 1
rule name
rule name
execute rule cost 72000 ns
@name mainly gets the name of the rule
Part3 @id
The meaning of using @id in the rule body is that if the current rule name is a string that can be converted into an integer, then @id is the integer value of the rule name. If the rule name string cannot be converted into an integer, then the value of @id is 0. This is to facilitate users to use the rule name as an integer parameter
package main import ( "fmt" "/bilibili/gengine/builder" "/bilibili/gengine/context" "/bilibili/gengine/engine" ) /* * use '@id',you can get rule name in rule content */ const atIDRule = ` rule "Test rule name 1" "rule desc" salience 10 begin println(@id) end rule " 100 " "rule desc" salience 20 begin x = @id println(x) end ` func TestAtId() { dataContext := () ("println", ) //init rule engine ruleBuilder := (dataContext) //resolve rules from string err := (atIDRule) if err != nil { panic(err) } eng := () err = (ruleBuilder, false) if err != nil { panic(err) } } type Data struct { M map[string]string } func (d *Data) exe() { println("hhhh") } func main() { TestAtId() }
100 0
Part4 @desc syntax
Get the description information of the current rule in the rule body, the type is a string
package main import ( "fmt" "/bilibili/gengine/builder" "/bilibili/gengine/context" "/bilibili/gengine/engine" "time" ) /* * use '@desc',you can get rule description in rule content */ const atDescRule = ` rule "rule name 1" "I'm a test description information 1" salience 100 begin desc = @desc Print(desc) Print(@name + " : " + @desc) end rule "rule name 2" //"I am a description, desc" sales 10begin desc = @desc Print(desc) Print(@name + " : " + @desc) end ` func main() { dataContext := () // ("Print", PrintName) ("Print", ) //init rule engine ruleBuilder := (dataContext) //resolve rules from string start1 := ().UnixNano() err := (atDescRule) end1 := ().UnixNano() println(("rules num:%d, load rules cost time:%d ns", len(), end1-start1)) if err != nil { panic(err) } eng := () // true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule err = (ruleBuilder, true) if err != nil { panic(err) } }
rules num:2, load rules cost time:3172000 ns
I'm a test description information 1
rule name 1: I am a test description information 1rule name 2 :
Note that if there is // is a comment, it cannot be parsed.
Part5 sal
Get the priority information of the current rule in the body of the rule, type int64
package main import ( "fmt" "/bilibili/gengine/builder" "/bilibili/gengine/context" "/bilibili/gengine/engine" "time" ) func main() { dataContext := () ("println", ) //init rule engine ruleBuilder := (dataContext) err := (` rule "1" salience 10 begin println(@sal) end rule "2" begin println(@sal) end `) if err != nil { panic(err) } eng := () start := ().UnixNano() // true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule err = (ruleBuilder, true) end := ().UnixNano() if err != nil { panic(err) } println(("execute rule cost %d ns", end-start)) }
10 0 execute rule cost 120000 ns
Part6 Comments
Supports single-line comments in rules, comments start with double slashes (//)
Part7 Custom variables
User-defined variables do not need to declare types. Variables defined in the rule are only visible to the current rule and are not visible to other rules (local variables). Use the (variable) data injected by dataContext, and all rules loaded into the gengine are visible to all rules (global variables)
Part8 Line number prompt when reporting an error
The syntax supported by gengine is a complete DSL syntax (can also be regarded as a complete language). When a gengine rule is executed, gengine will point out which line the specific error is on. Try to help users have a silky experience in every process detail of using gengine.
package main import ( "fmt" "/bilibili/gengine/builder" "/bilibili/gengine/context" "/bilibili/gengine/engine" ) var lineNumberRules = ` rule "line_number" "when execute error,gengine will give out error" begin //Println("golang", "hello", "world" ) //Cancel the slash comment and test different error reports in turn//if Println("golang", "hello") == 100 { // Println("golang", "hello") //} () end ` type MyStruct struct { } func (m *MyStruct) XX(s string) { println("XX") } func Println(s1, s2 string) bool { println(s1, s2) return false } func main() { dataContext := () //Inject custom functions ("Println", Println) ms := &MyStruct{} ("ms", ms) ruleBuilder := (dataContext) e1 := (lineNumberRules) if e1 != nil { panic(e1) } eng := () // true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule e2 := (ruleBuilder, true) if e2 != nil { println(("%+v", e2)) } }
[rule: "line_number" executed, error:
line 12, column 0, code: (), NOT FOUND Function: X ]
Part9 Multi-level Call Support
The form of third-level calls has been supported now, but calls above level three do not support other details:
1. When the syntax is C, or a=C, then C can be a specific value, variable, function or method, structure (pointer), map, slice, array, etc., for example, a=100, a = Mp["hello"], a = x, a = getMessage(p1,p2..) etc.
2. When the syntax is, or a=, then A must be a structure (pointer), C is the same as above, such as a = ["hello"], a = A.Field1, a = (p1,p2..) etc.
3. When the syntax is, or a=, then A and B must be structures (pointers), C is the same as above, such as a = ["hello"], a =.Field1, a= (p1, p2..) etc.
Then, if the syntax is right, ["hello"].C is illegal
package main import ( "fmt" "/bilibili/gengine/builder" "/bilibili/gengine/context" "/bilibili/gengine/engine" ) type B struct { Name string Mp map[string]string Ar [5]string Sl []int } func (b *B) Meth(s string) { println("--->", s) } func main() { type A struct { N string Ma map[string]string B *B } rule := ` rule "three level call" begin conc{ = "xiaoMing" ["hello"] = "world" [1] = "Calo" [2] = 3 x = [0] ([1]) = "kakaka" ["a"] = "b" } println(, ["hello"], [1], [2], x, , ["a"]) if [0] == 0 { println(true) } end ` b := B{ Name: "", Mp: make(map[string]string), Ar: [5]string{}, Sl: make([]int, 6), } pA := &A{ N: "", Ma: make(map[string]string), B: &b, } dataContext := () ("println", ) ("A", pA) ruleBuilder := (dataContext) e := (rule) if e != nil { panic(e) } gengine := () e = (ruleBuilder, true) if e != nil { panic(e) } println(, ["hello"], [1], [2]) }
---> Calo
xiaoMing world Calo 3 0 kakaka b
true
xiaoMing world Calo 3
The above is the detailed content of the golang rule engine gengine usage case. For more information about golang rule engine gengine, please follow my other related articles!