:toc: = 自定义语法元素 本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。 == 总览 * 自定义函数 ** 实现CustomFunction接口 ** 使用 Java 函数式接口 ** 通过注解扫描注册 ** 通过QLExpress脚本添加 ** 实现QLFunctionalVarargs * 自定义操作符 ** 实现CustomBinaryOperator ** 替换内置操作符 ** 使用 Java 函数式接口 ** 添加别名 ** 实现QLFunctionalVarargs * 扩展函数 ** 继承ExtensionFunction ** 实现QLFunctionalVarargs * 操作符与函数别名 == 自定义函数 === 实现CustomFunction接口 [source,java,indent=0] ---- package com.alibaba.qlexpress4.test.function; import com.alibaba.qlexpress4.runtime.Parameters; import com.alibaba.qlexpress4.runtime.QContext; import com.alibaba.qlexpress4.runtime.function.CustomFunction; public class HelloFunction implements CustomFunction { @Override public Object call(QContext qContext, Parameters parameters) throws Throwable { String tenant = (String)qContext.attachment().get("tenant"); return "hello," + tenant; } } ---- [source,java,indent=0] ---- Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); express4Runner.addFunction("hello", new HelloFunction()); String resultJack = (String)express4Runner.execute("hello()", Collections.emptyMap(), // Additional information(tenant for example) can be brought into the custom function from outside via attachments QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult(); assertEquals("hello,jack", resultJack); String resultLucy = (String)express4Runner .execute("hello()", Collections.emptyMap(), QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build()) .getResult(); assertEquals("hello,lucy", resultLucy); ---- === 使用 Java 函数式接口 [source,java,indent=0] ---- Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); // Function runner.addFunction("inc", (Function)x -> x + 1); // Predicate runner.addFunction("isPos", (Predicate)x -> x > 0); // Runnable runner.addFunction("notify", () -> { }); // Consumer runner.addFunction("print", (Consumer)System.out::println); Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals(2, r1); assertEquals(true, r2); ---- === 注解方式注册 [source,java,indent=0] ---- public static class MyFunctionUtil { @QLFunction({"myAdd", "iAdd"}) public int add(int a, int b) { return a + b; } @QLFunction("arr3") public static int[] array3(int a, int b, int c) { return new int[] {a, b, c}; } @QLFunction("concat") public String concat(String a, String b) { return a + b; } } ---- [source,java,indent=0] ---- Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil()); assertEquals(4, addResult.getSucc().size()); Object result = express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals(14, result); express4Runner.addStaticFunction(MyFunctionUtil.class); Object result1 = express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals(10, result1); Object result2 = express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals("aanull", result2); ---- === 通过QLExpress脚本添加 [source,java,indent=0] ---- public static class JoinFunction implements QLFunctionalVarargs { @Override public Object call(Object... params) { return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(",")); } } ---- === 实现QLFunctionalVarargs [source,java,indent=0] ---- Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); express4Runner.addVarArgsFunction("join", new JoinFunction()); Object resultFunction = express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals("1,2,3", resultFunction); ---- [source,java,indent=0] ---- Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); express4Runner.addVarArgsFunction("join", new JoinFunction()); Object resultFunction = express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals("1,2,3", resultFunction); ---- == 自定义操作符 === 实现CustomBinaryOperator并设置优先级 [source,java,indent=0] ---- Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD); Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); // precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16" assertEquals("16", r); ---- === 替换内置操作符 [source,java,indent=0] ---- Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); boolean ok = runner.replaceDefaultOperator("+", (left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString())); assertTrue(ok); Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals(3.5d, r); ---- === 使用 Java 函数式接口 [source,java,indent=0] ---- Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right); Object resultOperator = express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals("1,2,3", resultOperator); ---- === 实现QLFunctionalVarargs [source,java,indent=0] ---- Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); runner.addOperator("join", params -> params[0] + "," + params[1]); Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals("1,2", r); ---- == 扩展函数 === 继承ExtensionFunction [source,java,indent=0] ---- ExtensionFunction helloFunction = new ExtensionFunction() { @Override public Class[] getParameterTypes() { return new Class[0]; } @Override public String getName() { return "hello"; } @Override public Class getDeclaringClass() { return String.class; } @Override public Object invoke(Object obj, Object[] args) { String originStr = (String)obj; return "Hello," + originStr; } }; Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); express4Runner.addExtendFunction(helloFunction); Object result = express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals("Hello,jack", result); ---- === 实现QLFunctionalVarargs [source,java,indent=0] ---- // simpler way to define extension function express4Runner.addExtendFunction("add", Number.class, params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue()); QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); assertEquals(3, resultAdd.getResult()); ---- == QLFunctionalVarargs:一个对象同时定义三类操作 同一个QLFunctionalVarargs对象可同时用作函数、操作符与扩展函数的实现,便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计,详见下方示例与接口定义。背景讨论参考 link:https://github.com/alibaba/QLExpress/issues/407[issue407]: [source,java,indent=0] ---- Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); QLFunctionalVarargs allInOne = params -> { // sum numbers no matter how many args double sum = 0d; for (Object p : params) { if (p instanceof Number) { sum += ((Number)p).doubleValue(); } } return sum; }; // as function runner.addVarArgsFunction("sumAll", allInOne); // as operator runner.addOperator("+&", allInOne); // as extension function: first arg is the receiver, followed by call arguments runner.addExtendFunction("plusAll", Number.class, allInOne); Map ctx = new HashMap<>(); Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); assertEquals(6d, rf); assertEquals(5d, ro); assertEquals(6d, re); ---- === 接口定义 [source,java,indent=0] ---- package com.alibaba.qlexpress4.api; /** * Author: TaoKan */ @FunctionalInterface public interface QLFunctionalVarargs { Object call(Object... params); } ---- == 操作符与函数别名(亦支持关键字别名) [source,java,indent=0] ---- Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); // add custom function zero express4Runner.addFunction("zero", (String ignore) -> 0); // keyword alias assertTrue(express4Runner.addAlias("如果", "if")); assertTrue(express4Runner.addAlias("则", "then")); assertTrue(express4Runner.addAlias("否则", "else")); assertTrue(express4Runner.addAlias("返回", "return")); // operator alias assertTrue(express4Runner.addAlias("大于", ">")); // function alias assertTrue(express4Runner.addAlias("零", "zero")); Map context = new HashMap<>(); context.put("语文", 90); context.put("数学", 90); context.put("英语", 90); Object result = express4Runner .execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS) .getResult(); assertEquals(0, result); ---- == 说明与建议 - QLFunctionalVarargs 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。 - 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。 - 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。