-
三元/二元操作符:
- ? :
- 逻辑操作符: ||、&&
- 比较操作符: ==、!=、<、<=、>、>=、in。
- 计算操作符: +、-、*、/、%、**
- 位运算操作符: |、
^
、&、&^
、<<、>>
-
一元操作符:
- 自增: ++, 注意自增与自减只支持前缀, eg: ++2, 而2++是不被允许的, 因为表达式引擎只是运行一个表达式, 后缀的自增自减毫无意义
- 自减: --
- 取反: !
- 位取反: ~
- 负: -
-
注:
- 操作符优先级与Go语言操作符优先级一致(如果运算符相同的话)
- 按位取反位~而不是Go中的^
- 相对于Go增加 in 、** 两个操作符。
- 其中 in 操作符用于判断某值是否在一个集合中, eg: 1 in [1, 2.0, '3_'], 可以看到集合中元素的类型可以不一样, 其中集合可以只含有一个元素, 但也需要用 [ ]包围
- ** 为幂运算操作符, eg: 2 ** 3 的值为8
- 数值: 表达式中所有数值使用go中的float64表示, 数值书写可以写成1、1.0等都是允许的, 数值只支持10进制, 且不支持指数的表示方式
- 布尔: 书写为 true、false、t、f或四者的部分或全部大写都是可以的
- 字符串: 用小引号包裹, eg: 'go_expression', 支持转义。特别的, 如要表示小引号需要转义。
- 为了代码简洁, 传入的参数以及函数返回值如果为数值的话, 一律写作float64格式, 否则表达式执行将有可能不符合预期 如下代码所示
exp1, _ := goexpression.NewExpression("b(c, d) == a", true, map[string]goexpression.Function{
"b": func(params ...any) (any, error) {
if len(params) != 2 {
return nil, fmt.Errorf("error")
}
return params[0].(float64) + params[1].(float64), nil
},
})
// 为了性能, 表达式一次编译, 多次运行, 使用方需要实现表达式缓存, key为原始表达式, value 为 goexpression.NewExpression
_, _ = exp1.Execute(map[string]any{
"a": float64(100.0),
"c": float64(50.0),
"d": float64(50.0),
})
- 显然的, 可以嵌套调用, 形如b1(b2(b3())) && 1 in [num1(), num2()] 这样的表达式都是接受的
- 为了性能提升, 最好一次编译, 多次运行。即NewExpression方法调用之后, 得到的表达式可以传入不同的参数多次运行
- 表达式除了可以返回bool、string、float64 (三者也提供了转换函数)。还可以返回[]any切片, 即表达式只包含 "[item1, item2, ....]" 这种情况, 但是应该极少用到, 所以没有提供转换函数, 如有需求可以自行转换
- 注意变量在运算过程中是否改变, 不同使用场景有不同结果:
- 场景1: ++a == 1 && a == 2。注意, 此时如果执行时传入a的值为0, 那么 ++a == 1将为true, 但是当运行到 a == 2时, a还是等于0, 这有点反直觉, 当然也可以优化, 目前暂时不优化
- 场景2: func1(a) == 1 && func2(a) == 2, 如果a为非值类型, 比如是个map, 那么func1中对a的操作func2将会感知到, 这需要使用者知道
- 报错信息尤其是运算时期的报错只直接指明了类型错误, TODO: 优化语法分析阶段与执行阶段的报错信息