简单的表达式解析
olee-java is a fork of Jexl.
import com.meituan.olee.OneLineExpressionEvaluator;
import com.meituan.olee.evaluator.Expression;
OneLineExpressionEvaluator evaluator = new OneLineExpressionEvaluator();
evaluator.evaluate('1+x', new HashMap<String, Number>() {{
put("x", 2);
}});
// => 3
Expression expression = evaluator.compile('1+x');
expression.evalute(new HashMap<String, Number>() {{
put("x", 2);
}});
// => 3
TODO
支持 string
number
boolean
null
常量。
- 不支持,科学计数法数字。
- 不支持,十六进制。
使用 []
可以定义数组。如 [1,2,3,4,'5']
计算结果为 ArrayList<Object>
。
使用 {key: value}
可以定义对象。如 {name:'Nikola',age: 25,dob:'10-July-1856'}
计算结果为 HashMap<String, Object>
。
求值时,可以注入变量。如
HashMap<String, Number> variables = new HashMap<String, Number>() {{
put("x", 2);
}};
evaluator.evaluate('1+x', variables);
// => 3
.
[]
。 支持形如 a.b.c
a['b'].c
a.b[0][1]
等等。
默认的属性访问仅支持 Map
List
String
。如
{x:1}.x
=> 1
, {x:1}['x']
=> 1
, [1,2,3][0]
=> 1
, "abc"[2]
=> "c"
。
若需要支持其他类型实例,需要进行设置,如:
import com.meituan.olee.OneLineExpressionEvaluator;
import com.meituan.olee.evaluator.DefaultPropertyAccessor;
import com.meituan.olee.exceptions.EvaluateException;
class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
OneLineExpressionEvaluator evaluator = new OneLineExpressionEvaluator(new DefaultPropertyAccessor() {
@Override
public Object get(Object target, Object key, boolean computed) throws EvaluateException {
if (target instanceof User) {
if ("name".equals(key)) {
return ((User) target).getName();
}
if ("age".equals(key)) {
return ((User) target).getAge();
}
}
return super.get(target, key, computed);
}
});
User variables1 = new User("张三", 18);
Map<String, Object> variables2 = new HashMap<String, Object>() {{
put("user", variables1);
}};
assertEquals("张三", evaluator.evaluate("name", variables1));
assertEquals(18, (int) evaluator.evaluate("age", variables1));
assertEquals("张三", evaluator.evaluate("user.name", variables2));
assertEquals(18, (int) evaluator.evaluate("user.age", variables2));
也可以借助反射,或者 commons-beanutils
的 PropertyUtils.getProperty
。
?.
?.[]
?.()
可以在访问变量时,避免 NullPointerException
。效果等同于 js 的语法。
如 a?.b.c
,当 a==null
时,结果为 null
;
当 a!=null && a.b==null
时,会抛出 EvaluateException("Cannot read properties of null (reading c)")
。
操作符 | 符号 |
---|---|
取反 | ! |
+ |
+ |
- |
- |
操作符 | 符号 |
---|---|
加,拼接字符串 | + |
减 | - |
乘 | * |
除 | / |
整除 | // |
取模 | % |
指数 | ^ |
逻辑与 | && |
逻辑或 | || |
空值合并 | ?? |
操作符 | 符号 |
---|---|
相等 | == |
不等 | != |
大于 | > |
大于等于 | >= |
小于 | < |
小于等于 | <= |
判断元组是否在数组中 | in |
如 a ? b : c
。
可以在构造数组时,将数组或者 string 在语法层面展开;可以在构造对象时,将对象按 key-value 的方式展开。
表达式 | 结果 |
---|---|
[1,...[2,3],4] |
[1,2,3,4] |
[1,'23',4] |
[1,'2','3',4] |
{a:1,...{b:2,c:3},d:4} |
{a:1,b:2,c:3,d:4} |
- 新增二元操作符
import com.meituan.olee.grammar.BinaryOpGrammar;
import com.meituan.olee.exceptions.EvaluateException;
evaluator.addBinaryOp("_=", new BinaryOpGrammar(20) {
@Override
public Object apply(Object left, Object right) throws EvaluateException {
if (left == null && right == null) return true;
if (left instanceof String && right instanceof String) {
return ((String) left).equalsIgnoreCase(((String) right));
}
return false;
}
});
evaluator.evaluate("'FOO' _= 'foo'", null);
// => true
- 新增一元操作符
import com.meituan.olee.grammar.UnaryOpGrammar;
import com.meituan.olee.exceptions.EvaluateException;
evaluator.addUnaryOp("~", new UnaryOpGrammar(1000) {
@Override
public Object apply(Object right) throws EvaluateException {
if (right == null) return 0;
return (long) Math.floor(((Number) right).doubleValue());
}
});
evaluator.evaluate("~5.7+5", null);
// -> 10
- 删除二元操作符
evaluator.removeBinaryOp("+");
evaluator.evaluate("1+2", null); // => throws
优先级 | 符号 | 操作符 |
---|---|---|
10 | || ?? |
逻辑或、空值合并 |
20 | && |
逻辑与 |
30 | == != |
相等 |
40 | <= < >= > in |
比较 |
50 | + - |
加、减、拼接 |
60 | * / // % |
乘、除、整除、取余数 |
70 | ^ |
指数(右结合) |
80 | | | 管道 |
90 | ! + - |
一元操作符 |
100 | . ?. [] ?.[] |
成员访问 |
使用形如 def variableName = expression; returnExpression
的形式使用表达式内变量。
如:def a=1; def b=2; a+b
=> 3
,def a=1; def b=a+1; a+b
=> 3
支持方法调用,如:
import com.meituan.olee.Callback;
Map<String, Object> variables = new HashMap<String, Object>() {{
put("foo", 10);
put("double", (Callback) ((args) -> ((Number) args[0]).longValue() * 2));
}};
evaluator.evaluate("double(foo)+3", variables);
// => 23
- 需要注意,方法需要定义在
变量
中。
|
可以简化方法调用。
fun(arg1, arg2, arg3)
可以写成 arg1|fun(arg2, arg3)
的形式。
fun(arg1)
可以写成 arg1|fun()
的形式,也可以省略括号 arg1|fun
。
比如:baz(bar(foo,1))
可以简化为 foo|bar(1)|baz
,更容易理解。
需要注意,如果是 foo.bar(baz)
,转为管道形式应为 baz|(foo.bar)
,不能是 baz|foo.bar
。因为后者等同于 (baz|foo).bar
。
除了使用 variables
的方式注入函数外,还可以使用 addTransform
注入函数,如:
OneLineExpressionEvaluator evaluator = new OneLineExpressionEvaluator();
evaluator.addTransform(
"half",
(args) -> ((Number) args[0]).doubleValue() / 2
);
evaluator.evaluate("half(6)", null);
// => 3
evaluator.evaluate("6|half", null);
// => 3
定义函数的形式是 fn () => expression
或者 fn (a, b, c) => expression
。
示例:
import com.meituan.olee.Callback;
evaluator.addTransform(
"filter",
(args) -> ((List<?>) args[0]).stream()
.filter((item) -> (Boolean) ((Callback) args[1]).apply(item))
.collect(Collectors.toList())
);
evaluator.addTransform(
"map",
(args) -> ((List<?>) args[0]).stream()
.map((item) -> ((Callback) args[1]).apply(item))
.collect(Collectors.toList())
);
Map<String, Object> variables = new HashMap<String, Object>() {{
put("bar", new ArrayList<Object>() {{
add(Collections.singletonMap("tek", "hello"));
add(Collections.singletonMap("tek", "baz"));
add(Collections.singletonMap("tek", "baz"));
add(Collections.singletonMap("tok", "baz"));
}});
}};
// => { bar: [{tek: "hello"}, {tek: "baz"}, {tek: "baz"}, {tok: 'baz'}]}
assertEquals(
new ArrayList<Object>() {{
add(Collections.singletonMap("tek", "baz"));
add(Collections.singletonMap("tek", "baz"));
}},
evaluator.evaluate("bar|filter(fn (a) => a.tek == 'baz')", variables)
);
// => [{tek: "baz"}, {tek: "baz"}]
assertEquals(
new ArrayList<Object>() {{
add("1hello");
add("1baz");
add("1baz");
add("1baz");
}},
evaluator.evaluate("bar|map(fn (a) => '1'+(a.tek||a.tok))", variables)
);
// => ["1hello", "1baz", "1baz", "1baz"]
evaluator.evaluate("bar|filter(fn (a) => a.tek != null)|map(fn (a) => a.tek)", variables);
// => ["hello", "baz", "baz"]
可以使用 @
@0
@1
~ @9
的特殊标识符来定义一个简版函数。
@
@0
表示第 0 个函数参数,@1
~ @9
分别表示第 1 ~ 9 个函数参数。
比如:@.x + @1
表示 fn (a, b) => a.x + b
。
示例(代码同定义函数):
assertEquals(
new ArrayList<Object>() {{
add(Collections.singletonMap("tek", "baz"));
add(Collections.singletonMap("tek", "baz"));
}},
evaluator.evaluate("bar|filter(@.tek == 'baz')", variables)
);
// => [{tek: "baz"}, {tek: "baz"}]
assertEquals(
new ArrayList<Object>() {{
add("1hello");
add("1baz");
add("1baz");
add("1baz");
}},
evaluator.evaluate("bar|map('1'+(@.tek||@.tok))", variables)
);
// => ["1hello", "1baz", "1baz", "1baz"]
evaluator.evaluate("bar|filter(@.tek != null)|map(@.tek)", variables);
// => ["hello", "baz", "baz"]
可以看出这个示例中最后一个表达式 "bar|filter(@.tek != null)|map(@.tek)"
非常精简。
需要注意,表达式不会修改传入的变量,也不存在赋值操作。
如果表达式格式错误、不完整等,会抛出 ParseException
错误。比如:
a.b ~+= c.d
抛出ParseException("Invalid expression token: ~")
。
如果操作符不支持对应类型、属性访问错误等,会抛出 EvaluateException
错误。比如:
[1,2,3]+{x:1,y:2}
抛出EvaluateException("unsupported type for +")
。{x:1}.y.z
抛出EvaluateException("Cannot read properties of null (reading z)")
。(10).x
抛出EvaluateException("Not supported!")
。