1. 前言
小伙伴大家好,在上一篇文章我们简单初探了QLExpress表达式引擎,我们简单写了一个HelloWorld的程序,并成功的运行期望的结果。在本篇文章中我们来熟悉一下QLExpress的语法,因为我们在后面简道云公式实战的时候,需要用到这些语法。废话少说,直接开始。
2. 基础语法
相信大家也下载了QLExpress的源码,在下载的源码中有一个readme文件,在这个文件中,源码作者已经介绍了语法以及QLExpress的用法。大家可以详细阅读,以及运行相应的测试文件。如图所示:
2.1 操作符和java对象
2.1.1 操作符
在readme说明文件中,我们知道QLExpress支持的操作符有:
+,-,*,/,<,>,<=,>=,==,!=,<>,%,++,–- in 【类似sql】,like【sql语法】,&&,|| 支持for,break、continue、if then else 等标准的程序控制逻辑
上述的的操作符,笔者都会写测试用例。我们先从简单的四则运算开始:
2.1.1.1 四则运算
四则运算需求提出: 需求1:编写java程序使用QLExpress表达式引擎,计算"1+2-3*4/5" 表达式的结果,并在控制台打印结果 需求2:编写java程序使用QLExpress表达式引擎,计算"1 加 2 减 3 乘 4 除 5"表达式的结果,并在控制台打印结果
当我们拿到需求的时候,首先第一步我们要明确需求,需求是让我们借助QLExpress表达式引擎,来计算表达式的结果。
表达式中包括英文"+-*/"和中文"加 减 乘 除"。需求中并没有提出计算结果精度的问题(虽然需求没有定下来,我们要支持高精度计算)。
基于上述的讨论后,码农就拿起键盘一头扎进了QLExpress代码学习中,以及各种百度,CSDN搜索解决方案。最终码农A编写了需求1如下代码:
/*** * "1+2-3*4/5" */ @Test public void test1() throws Exception{ // @param isPrecise 是否需要高精度计算支持 // @param isTrace 是否跟踪执行指令的过程 ExpressRunner runner = new ExpressRunner(true, true); String expressStr = "1+2-3*4/5"; // isTrace object rst = runner.execute(expressStr, null, null, true, true); System.out.println(rst); }
码农A很自豪的讲起了自己的解决方案,QLExpress引擎提供了isPrecise 和 isTrace两个属性,在ExpressRunner 初始化的时候可以指定isPrecise 是否需要高精度计算支持和isTrace 是否跟踪执行指令的过程。isTrace在调试阶段很有用,可以查看表达式执行指令的整个过程,在程序上线时,建议设置为false避免浪费资源。
如果isPrecise=true代码运行的结果为0.6000000000;如果isPrecise = false代码运行结果为1.代码运行如图所示:
码农A沮丧的说,我解决了需求1,但是需求2没有找到解决方案。项目组中有人找到需求2的解决方案吗?大家可以交流一下。一向低调的码农B开口了。
码农B说:通过阅读QLExpress代码,找到一个方法
addOperatorWithAlias("加","+",null)。
/** * 添加操作符和关键字的别名,同时对操作符可以指定错误信息。 * 例如:addOperatorWithAlias("加","+",null) * * @param keyWordName * @param realKeyWordName * @param errorInfo * @throws Exception */ public void addOperatorWithAlias(String keyWordName, String realKeyWordName, String errorInfo) throws Exception { if (errorInfo != null && errorInfo.trim().length() == 0) { errorInfo = null; } //添加函数别名 if (this.manager.isFunction(realKeyWordName)) { this.manager.addFunctionName(keyWordName); this.operatorManager.addOperatorWithAlias(keyWordName, realKeyWordName, errorInfo); return; } NodeType realNodeType = this.manager.findNodeType(realKeyWordName); if (realNodeType == null) { throw new QLException("关键字:" + realKeyWordName + "不存在"); } boolean isExist = this.operatorManager.isExistOperator(realNodeType.getName()); if (!isExist && errorInfo != null) { throw new QLException("关键字:" + realKeyWordName + "是通过指令来实现的,不能设置错误的提示信息,errorInfo 必须是 null"); } if (!isExist || errorInfo == null) { //不需要新增操作符号,只需要建立一个关键子即可 this.manager.addOperatorWithRealNodeType(keyWordName, realNodeType.getName()); } else { this.manager.addOperatorWithLevelOfReference(keyWordName, realNodeType.getName()); this.operatorManager.addOperatorWithAlias(keyWordName, realNodeType.getName(), errorInfo); } }
此方法作用就是添加操作符和关键字的别名,于是就构思出来如下代码【test2方法运行报错】:
@Test public void test2() throws Exception{ ExpressRunner runner = new ExpressRunner(true, true); String expressStr = "1 加 2 减 3 乘 4 除 5"; runner.addOperatorWithAlias("加","+","加 的这个操作符有问题"); runner.addOperatorWithAlias("减","-","减 的这个操作符有问题"); runner.addOperatorWithAlias("乘","*","乘 的这个操作符有问题"); runner.addOperatorWithAlias("除","/","除 的这个操作符有问题"); // isTrace object rst = runner.execute(expressStr, null, null, true, true); System.out.println(rst); }
经测试发现程序报错:
com.ql.util.express.exception.QLException: * 不能被设置别名:com.ql.util.express.instruction.op.OperatorMultiplyDivide.<init>(java.lang.String, java.lang.String, java.lang.String) 如下图所示:
通过报错信息可知OperatorMultiplyDivide.<init>方法有问题,于是就进入OperatorMultiplyDivide源码,模仿着写了
OperatorMultiply 类和 OperatorDivide类,即把 OperatorMultiplyDivide类拆分成两个类。
/** * 类描述:乘操作 * * @author admin * @version 1.0.0 * @date 2023/11/13 17:17 */ public class OperatorMultiply extends Operator { @Override public object executeInner(object[] list) throws Exception { return executeInner(list[0], list[1]); } public object executeInner(object op1, object op2) throws Exception { object result = OperatorOfNumber.multiply(op1, op2, this.isPrecise); return result; } } /** * 类描述:除操作 * * @author admin * @version 1.0.0 * @date 2023/11/13 17:21 */ public class OperatorDivide extends Operator { @Override public object executeInner(object[] list) throws Exception { return executeInner(list[0], list[1]); } public object executeInner(object op1, object op2) throws Exception { object result = OperatorOfNumber.divide(op1, op2, this.isPrecise); return result; } }
基于上面两个类,又写了test3方法(该方法运行报错)
@Test public void test3() throws Exception{ ExpressRunner runner = new ExpressRunner(true, true); String expressStr = "1 加 2 减 3 乘 4 除 5"; runner.addOperator("*",new OperatorMultiply()); runner.addOperator("/",new OperatorDivide()); runner.addOperatorWithAlias("加","+","加 的这个操作符有问题"); runner.addOperatorWithAlias("减","-","减 的这个操作符有问题"); runner.addOperatorWithAlias("乘","*","乘 的这个操作符有问题"); runner.addOperatorWithAlias("除","/","除 的这个操作符有问题"); // isTrace object rst = runner.execute(expressStr, null, null, true, true); System.out.println(rst); }
运行报错:java.lang.RuntimeException: 节点类型定义重复:* 定义1=*:TYPE=KEYWORD 定义2=*:TYPE=KEYWORD
根据错误信息猜测应该是runner.addOperator("*",new OperatorMultiply());源码中已经有了,于是就找到OperatorFactory工厂类(笔者删减了其他代码)发现确实在这个工厂类中已经添加addOperator("*", new OperatorMultiplyDivide("*"));addOperator("/", new OperatorMultiplyDivide("/"));
public OperatorFactory(boolean isPrecise) { this.isPrecise = isPrecise; addOperator("!", new OperatorNot("!")); addOperator("*", new OperatorMultiplyDivide("*")); addOperator("/", new OperatorMultiplyDivide("/")); }
既然有了那我就直接用汉字 不就能满足需求了,于是就又写了以下代码test4(运行通过)
@Test public void test4() throws Exception{ ExpressRunner runner = new ExpressRunner(true, true); String expressStr = "1 加 2 减 3 乘 4 除 5"; runner.addOperator("乘",new OperatorMultiply()); runner.addOperator("除",new OperatorDivide()); runner.addOperatorWithAlias("加","+","加 的这个操作符有问题"); runner.addOperatorWithAlias("减","-","减 的这个操作符有问题"); // isTrace object rst = runner.execute(expressStr, null, null, true, true); System.out.println(rst); }
如果isPrecise=true代码运行的结果为0.6000000000;如果isPrecise = false代码运行结果为1
2.1.1.2 其余操作符测试案例
取余操作
/*** * 测试操作符mod * isTrace:输出脚本的编译解析过程,一般对于业务系统来说关闭之后会提高性能。 */ @Test public void mod() throws Exception{ ExpressRunner runner = new ExpressRunner(false, true); String expressStr = "20 % 5"; // isTrace Object rst = runner.execute(expressStr, null, null, true, true); System.out.println(rst); }
加加操作
注意:++这个操作,分为前加加和后加加 如i++ 和++i,在QLExpress中前加加会报错。
/*** * 测试++ * @throws Exception */ @Test public void addPlusAfter() throws Exception{ ExpressRunner runner = new ExpressRunner(false, true); DefaultContext<String, Object> context = new DefaultContext<>(); context.put("a",11); context.put("b",12); String expressStr = "a++"; // isTrace Object rst = runner.execute(expressStr, context, null, true, true); // 打印此时a的值是多少 Object a_now = context.get("a"); System.out.println(a_now); System.out.println(rst); } /*** * 测试++ 反例 * @throws Exception com.ql.util.express.exception.QLCompileException: 程序错误,不满足语法规范,没有匹配到合适的语法,最大匹配致[0:-1] */ @Test public void addPlusBefore() throws Exception{ ExpressRunner runner = new ExpressRunner(false, true); DefaultContext<String, Object> context = new DefaultContext<>(); context.put("a",11); context.put("b",12); String expressStr = "++a"; // isTrace Object rst = runner.execute(expressStr, context, null, true, true); // 打印此时a的值是多少 Object a_now = context.get("a"); System.out.println(a_now); System.out.println(rst); }
减减操作
注意:--这个操作,分为前减减和后减减 如i-- 和--i,在QLExpress中前减减会报错。
/*** * 测试-- * @throws Exception */ @Test public void minusPlusAfter() throws Exception{ ExpressRunner runner = new ExpressRunner(false, true); DefaultContext<String, Object> context = new DefaultContext<>(); context.put("a",11); context.put("b",12); String expressStr = "a--"; // isTrace Object rst = runner.execute(expressStr, context, null, true, true); // 打印此时a的值是多少 Object a_now = context.get("a"); System.out.println(a_now); System.out.println(rst); } /*** * 测试-- 反例 * @throws Exception com.ql.util.express.exception.QLCompileException: 程序错误,不满足语法规范,没有匹配到合适的语法,最大匹配致[0:-1] */ @Test public void minusPlusBefore() throws Exception{ ExpressRunner runner = new ExpressRunner(false, true); DefaultContext<String, Object> context = new DefaultContext<>(); context.put("a",11); context.put("b",12); String expressStr = "--a"; // isTrace Object rst = runner.execute(expressStr, context, null, true, true); // 打印此时a的值是多少 Object a_now = context.get("a"); System.out.println(a_now); System.out.println(rst); }
不等于操作符
注意:不等于符号是 != 切记不要使用<>
/*** * 测试 != 符号 * @throws Exception */ @Test public void ne() throws Exception{ ExpressRunner runner = new ExpressRunner(false, true); String expressStr = "20 != 5"; // isTrace Object rst = runner.execute(expressStr, null, null, true, true); System.out.println(rst); } /*** * 测试 <> 符号 反例 * @throws Exception com.ql.util.express.exception.QLCompileException: 程序错误,不满足语法规范,没有匹配到合适的语法,最大匹配致[0:1] */ @Test public void ne2() throws Exception{ ExpressRunner runner = new ExpressRunner(false, true); DefaultContext<String, Object> context = new DefaultContext<>(); context.put("a",11); context.put("b",12); String expressStr = "a <> b"; // isTrace Object rst = runner.execute(expressStr, null, null, true, true); System.out.println(rst); }
in操作
/*** * in 测试 */ @Test public void in() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = " 3+4 in (8+3,7,9)"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
like操作
注意 ab如果作为一个值的话要用引号包裹,否则QLExpress会把ab当做一个变量
/*** * like测试 */ @Test public void like() throws Exception{ // 注意ab必须用字符串包裹,不然会把ab当做变量 ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "'ab' like '%a%'"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
&&操作
/*** * && * @throws Exception */ @Test public void and() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "false && true"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
||操作
/*** * || * @throws Exception */ @Test public void or() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "false || true"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
三目表达式测试
/*** * 测试三目表达式 */ @Test public void san() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "int a=1;int b=2;a>b?a:b;"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
for 循环操作
注意:while循环测试报错
/*** * 测试循环 * @throws Exception */ @Test public void loop() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "int sum=10;" + "for(int i = 0; i < 1; i = i + 1){" + " println(i); sum=sum+i;" + "}" + "return sum"; // isTrace Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); System.out.println("--------------java for loop------------"); int sum=10; for (int i=0;i<1;i+=1) { System.out.println(i); sum = sum + i; } System.out.println(sum); } /*** * while 循环 * @throws Exception */ @Test public void loop2() throws Exception { ExpressRunner runner = new ExpressRunner(false, true); String expressStr ="while(true) {println('1')}"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
if_then_else操作符
/*** * if_then_else 测试 */ @Test public void if_then_else() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "if 1==1 then {return 10}else{return 100}"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); } @Test public void if_() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "if(1==1) { return 100;}"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); } @Test public void if_else() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "if(1==2) { return 100;} else {" + "return -1;}"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); } @Test public void if_then() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "if(1==1) then{ return 100;}"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
2.1.1.3 java对象操作
在QLExpress表达式引擎中,是支持操作java对象的,比如我们在一个表达式中需要调用一个java对象的方法作为处理的函数。下面我们还是通过测试用例,来说明java对象操作的知识点
1. 首先创建Bean对象
/** * 类描述: * * @author admin * @version 1.0.0 * @date 2023/11/13 16:03 */ public class Student { // 学号 private String sn; // 姓名 private String name; // 年龄 private int age; public Student(){} public Student(String sn, String name, int age) { this.sn = sn; this.name = name; this.age = age; } public String getSn() { return sn; } public void setSn(String sn) { this.sn = sn; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public static String print(String msg) { String rst = "Student print method exe::::::"+msg; return rst; } }
2.我们都知道java对象的生命周期是从初始化开始的,那么我们就用QLExpress表达式的形式初始化一个对象
/*** * 测试java对象,测试创建对象 */ @Test public void obj_init() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "import com.ql.util.express.self.base.Student; Student stu = new Student('000','tom',18); return stu.getName()"; Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
通过表达式我们可知:QLExpress是支持import的关键字
3.测试调用Student对象的print方法
/*** * 测试调用java对象 addFunctionOfClassMethod */ @Test public void obj_method_addFunctionOfClassMethod() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "打印('jim')"; // 使用QLExpress用于将一个用户自己定义的对象(例如Spring对象)方法转换为一个表达式计算的函数 runner.addFunctionOfClassMethod("打印",Student.class.getName(),"print", new String[]{"String"},null); Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
说明:addFunctionOfClassMethod方法含义为
用于将一个用户自己定义的对象(例如Spring对象)方法转换为一个表达式计算的函数。
该方法的源码为:
/** * 添加一个类的函数定义,例如:Math.abs(double) 映射为表达式中的 "取绝对值(-5.0)" * * @param name 函数名称 * @param className 类名称 * @param functionName 类中的方法名称 * @param parameterTypes 方法的参数类型名称 * @param errorInfo 如果函数执行的结果是false,需要输出的错误信息 * @throws Exception */ public void addFunctionOfClassMethod(String name, String className, String functionName, String[] parameterTypes, String errorInfo) throws Exception { OperatorSelfDefineClassFunction operatorSelfDefineClassFunction = new OperatorSelfDefineClassFunction(name, className, functionName, parameterTypes, null, null, errorInfo); this.addFunction(name, operatorSelfDefineClassFunction); }
4.测试调用java对象的方法2
/*** * 测试调用java对象 addFunctionOfClassMethod */ @Test public void obj_method_addFunctionOfServiceMethod() throws Exception{ ExpressRunner runner = new ExpressRunner(false, false); String expressStr = "打印('jim')"; // 使用QLExpress runner.addFunctionOfServiceMethod("打印",new Student(),"print", new String[]{"String"},null); Object rst = runner.execute(expressStr, null, null, true, false); System.out.println(rst); }
addFunctionOfClassMethod 方法用于将用户定义的对象的方法转换为表达式计算的函数
/** * 用于将一个用户自己定义的对象(例如Spring对象)方法转换为一个表达式计算的函数 * * @param name * @param serviceObject * @param functionName * @param parameterTypes * @param errorInfo * @throws Exception */ public void addFunctionOfServiceMethod(String name, Object serviceObject, String functionName, String[] parameterTypes, String errorInfo) throws Exception { OperatorSelfDefineServiceFunction operatorSelfDefineServiceFunction = new OperatorSelfDefineServiceFunction( name, serviceObject, functionName, parameterTypes, null, null, errorInfo); this.addFunction(name, operatorSelfDefineServiceFunction); }
总结:
通过测试QLExpress操作java对象,我们学习了addFunctionOfClassMethod 方法,我们可以把一个java类中的方法,转化为QLExpress表达式函数。
最近笔者创建了一个圈子https://pc.fenchuan8.com/#/index?forum=58055&yqm=9HKS
欢迎大家加入一起交流学习。