The rule engine is implemented with go-yacc, parsing the calculation string and calculating the result. It sas supported input variables, arithmetic operations, logical operations, decimal float and some built-in functions, which will be extended later as needed.
go get github.com/uyouii/rule_enginerule_engine library requires Go version >=1.7
example project: rule_engine_example
package main
import (
"fmt"
"github.com/uyouii/rule_engine"
)
func main() {
params := []*rule_engine.Param{
rule_engine.GetParam("i", 100),
rule_engine.GetParam("f", 3.5),
rule_engine.GetParam("s", "hello world"),
rule_engine.GetParam("b", false),
rule_engine.GetParam("d", 3.3),
// get decimal from string
rule_engine.GetParamWithType("d2", rule_engine.ValueTypeDecimal, "3.3"),
}
// use decimal: true
praser, err := rule_engine.GetNewPraser(params, true)
if err != nil {
panic(err)
}
exampleList := []string{
// integrate
`4 * (2 + 3) - 5 * 3`,
// float
`3.0 * (2.5 - 3)`,
// logic
`1 < 2 and 2 < 3 and 4.0 != 4.0001`,
`1 > 2 && 1 < 2`,
// func exapmle
`max(min(10.0, 20, 30), len("vstr"))`,
`min(len("test"), abs(-4.5), min(5,6))`,
`upper("abc") == "ABC"`,
`startWith("hello world", "hel")`,
`int(97) + 3 == max(100, -1)`,
`regexMatch("^0[xX][a-fA-F0-9]+", "0xasf4")`,
`string({{d}}) == "3.3"`,
// use param
`min(len({{s}}), {{i}}, {{f}}, {{d}})`,
`int({{i}} / {{f}})`,
`{{d}} * 10 - 3 - int({{i}} / {{f}})`,
`{{d2}} - {{d}}`,
// if else
`{{d}} * 10 if len({{s}}) > 10 else {{f}} / 10`,
}
for _, example := range exampleList {
res, err := praser.Parse(example)
if err != nil {
panic(err)
}
fmt.Printf("%v --> %v\n", example, res.Value)
}
}Output
4 * (2 + 3) - 5 * 3 --> 5
3.0 * (2.5 - 3) --> -1.5
1 < 2 and 2 < 3 and 4.0 != 4.0001 --> true
1 > 2 && 1 < 2 --> false
max(min(10.0, 20, 30), len("vstr")) --> 10
min(len("test"), abs(-4.5), min(5,6)) --> 4
upper("abc") == "ABC" --> true
startWith("hello world", "hel") --> true
int(97) + 3 == max(100, -1) --> true
regexMatch("^0[xX][a-fA-F0-9]+", "0xasf4") --> true
string({{d}}) == "3.3" --> true
min(len({{s}}), {{i}}, {{f}}, {{d}}) --> 3.3
int({{i}} / {{f}}) --> 28
{{d}} * 10 - 3 - int({{i}} / {{f}}) --> 2
{{d2}} - {{d}} --> 0
{{d}} * 10 if len({{s}}) > 10 else {{f}} / 10 --> 33https://pkg.go.dev/github.com/uyouii/rule_engine
// 1. use GetNewPraser to get a New Praser
// the params are the variable will be used in the calculation
// is set useDecimal, all the float in the param and calculation will be changed to decimal.
func GetNewPraser(params []*Param, useDecimal bool) (*Praser, error)
// 2. use Parse to get the result
func (p *Praser) Parse(str string) (*TokenNode, error)
// for example
praser, _ := rule_engine.GetNewPraser(params, true)
res, _ := praser.Parse(`1 + 1`)
fmt.Printf(res.Value)
2When set the struct Param:
type Param struct {
Name string // value name
Type ValueType // value type
Value interface{} // value
}if only set Nameand Value, then Praser will try to parse the Type from Value
// for example
p1 := GetParamWithType("x", rule_engine.ValueTypeDecimal, "3.3")
variable {{x}} will be prase to decimal 3.3if both set Name , Value and Type, Praser will try to reparse the Value according to Type.
// for example
p2 := GetParam("y", "3.3")
variable {{y}} will be prase to string "3.3"the detail about the variable can see Support Variable section.
the Api Parse will return a TokenNode as Result.
type TokenNode struct {
ValueType ValueType // result type, can see ValueType
Value interface{} // result value
}// if want get interface{} res, can use
func(t *TokenNode) GetValue() interface{}
// if want get detail type value, can use
func (t *TokenNode) GetInt() int64
func (t *TokenNode) GetBool() bool
func (t *TokenNode) GetFloat() float64
func (t *TokenNode) GetDecimal() decimal.Decimal
func (t *TokenNode) GetString() string| Type | ValueType in Code |
|---|---|
| bool | ValueTypeBool |
| string | ValueTypeString |
| int | ValueTypeInteger |
| float | ValueTypeFloat |
| decimal | ValueTypeDecimal |
notice:the implementation of decimal in the project depends on the https://github.com/shopspring/decimal
The value type will be implicitly reduced to the more precise type in calculation.
if int meet float, will be treated as float, and the result is float.
If int or float meet decimal, will be treated as decimal, and the result is decimal.
int >> float >> decimalfor example:
int + float = float
float * decimal = decimal
decimal - int = decimal
max(int, float, deciamal) = decimal| Operator | Name | Support Types |
|---|---|---|
() |
Parentheses | ALL |
{{var_name}} |
External Variable | ALL |
- |
Negative | int, float, decimal |
! not |
Not | bool |
+ |
Addition | int, float, decimal |
- |
Subtraction | int, float, decimal |
* |
Multiplication | int, float, decimal |
/ |
Division | int, float, decimal |
% |
Mod | int |
> |
Larger | int, float, decimal |
>= |
Larger or Equal | int, float, decimal |
< |
Less | int, float, decimal |
<= |
Less or Equal | int, float, decimal |
== |
Equal | ALL |
!= |
NotEqual | ALL |
and && |
And | bool |
or || |
Or | bool |
x if c else y |
Ternary operator | ALL, c must bool |
Decreasing priority from top to bottom.
() {{var_name}}
! not -(Negative)
* / %
+ -
> >= < <=
== !=
if else (ternary operator)
and && or ||rule_engine supports variable in calculation, by use {{}}(double braces) to enclose the variable name.
the varibale name and value should be passed in the interface, with the type: Param
can see the example:
d: type decimal, value 3.3
i: type int, value 100
{{d}} * 100 + 3 - int({{i}} * 10 / 3) --> 0the variable type can be int, float, decimal, bool, string
| Function Name | Descrption |
|---|---|
| len() | length of the string |
| min() | min of the args |
| max() | max of the args |
| abs() | Abs |
| upper() | Upper of the string |
| lower() | Lower of the string |
| startWith() | check string start with some prefix |
| endWith() | check string end with some suffix |
| regexMatch() | Regex Match |
| int() | change arg to int type |
| float() | change arg to float type |
| decimal() | change arg to decimal type |
| string() | change arg to string type |
// length of the string
// param {string} input string
// return {bool}
bool len(str)
e.g.
len("test")
4// min of the args
// param {int/bool/decimal}
// return {int/bool/decimal}, result type will be the most precise type.
any min(x, y, z, ...)
e.g.
min(1, len("test"), 6.8)
6.8// max of the args
// param {int/bool/decimal}
// return {int/bool/decimal}, result type will be the most precise type.
any max(x, y, z, ...)
e.g.
max(1, len("test"), 6.8)
6.8// Abs of the arg
// param {int/bool/decimal} x
// return {int/bool/decimal}, result type accornding to the input type
any abs(x)
e.g.
abs(-1.1)
1.1// the upper string of the input
// param {string} s
// return {string}
string upper(s string)
e.g.
upper("Hello World")
"HELLO WORLD"// the lower string of the input
// param {string} s
// return {string}
string lower(s string)
e.g.
lower("Hello World")
"hello world"// check s start with prefix
// param {string} s
// param {string} prefix
// return {bool}
bool startWith(s string, prefix string)
e.g.
startWith("Hello World", "Hello")
true// check s start with suffix
// param {string} s
// param {string} suffix
// return {bool}
bool endWith(s string, suffix string)
e.g.
startWith("Hello World", "World")
true// whether the string s contains any match of the regular expression pattern.
// param {string} pattern, the regex pattern
// param {string} the check string
// return {bool}
bool regexMatch(pattern string, s string)
e.g.
regexMatch("^test$", "test")
true
regexMatch("(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]","https://www.baidu.com")
true// change value to integer type
// param {int/float/decimal/string} v
// return {int}
int int(v any)
e.g.
int("100")
100
int(33.33)
33// change value to float type
// param {int/float/decimal/string} v
// return {float}
float float(v any)
e.g.
float("33.3")
33.3
float(100)
100// change value to decimal type
// param {int/float/decimal/string} v
// return {decimal}
decimal decimal(v any)
e.g.
decimal("33.3")
33.3
decimal(100)
100// change value to string type
// param {int/float/decimal/string} v
// return {string}
string string(v any)
e.g.
string(33.3)
"33.3"
string(100)
"100"This is the BNF(Backus Normal Form) of the rule_engine, how to reduce the input and calculate the result.
top :
TRANSLATION_UNIT
TRANSLATION_UNIT :
LOGIC_EXPR END
LOGIC_EXPR :
LOGIC_OR_EXPR
LOGIC_OR_EXPR :
LOGIC_AND_EXPR
| LOGIC_OR_EXPR OR LOGIC_AND_EXPR
LOGIC_AND_EXPR :
THIRD_OPER_EXPR
| LOGIC_AND_EXPR AND THIRD_OPER_EXPR
THIRD_OPER_EXPR :
EQUAL_EXPR
| EQUAL_EXPR IF THIRD_OPER_EXPR ELSE THIRD_OPER_EXPR
EQUAL_EXPR :
RELATION_EXPR
| EQUAL_EXPR EQ RELATION_EXPR
| EQUAL_EXPR NE RELATION_EXPR
RELATION_EXPR :
ADD_EXPR
| ADD_EXPR '<' RELATION_EXPR
| ADD_EXPR '>' RELATION_EXPR
| ADD_EXPR LE RELATION_EXPR
| ADD_EXPR GE RELATION_EXPR
ADD_EXPR :
MUL_EXPR
| ADD_EXPR '+' MUL_EXPR
| ADD_EXPR '-' MUL_EXPR
MUL_EXPR :
UNARY_EXPR
| MUL_EXPR '*' UNARY_EXPR
| MUL_EXPR '/' UNARY_EXPR
| MUL_EXPR '%' UNARY_EXPR
UNARY_EXPR :
POST_EXPR
| '-' PRIMARY_EXPR
| NOT PRIMARY_EXPR
POST_EXPR :
PRIMARY_EXPR
| IDENTIFIER '(' ARGUMENT_EXPRSSION_LIST ')'
| IDENTIFIER '(' ')'
ARGUMENT_EXPRSSION_LIST :
LOGIC_EXPR
| ARGUMENT_EXPRSSION_LIST ',' LOGIC_EXPR
PRIMARY_EXPR :
INTEGER
| FLOAT
| BOOL
| STRING
| ERROR
| '(' LOGIC_EXPR ')'
| VALUE_EXPR
VALUE_EXPR :
IDLEFT VAR_NAME IDRIGHT
VAR_NAME :
IDENTIFIER
| VAR_NAME '.' IDENTIFIER
| VAR_NAME '.' INTEGER