diff --git a/README.md b/README.md index 7e7aeec547be2a9e0611faabb4bc7d7e1138dc85..0d56638148d0cf5564cf318e5686c919c8dea4fa 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # QLExpress基本语法 -## 背景介绍 +# 一、背景介绍 由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。 在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神,于2012年开源。 -## 依赖和调用说明 +# 二、依赖和调用说明 ```xml @@ -25,7 +25,7 @@ String express = "a+b*c"; Object r = runner.execute(express, context, null, true, false); System.out.println(r); ``` - +# 三、语法介绍 ## 1、操作符和java对象操作 ### 普通java语法 ``` @@ -270,4 +270,189 @@ var : 语文 System.out.println(map.get(key)); } +``` + + +# 四、运行参数和API列表介绍 + +QLExpressRunner如下图所示,从语法树分析、上下文、执行过程三个方面提供二次定制的功能扩展。 + +![QlExpress-detail.jpg](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/dec904b003aba15cbf1af2726914ddee.jpg) + +## 1、属性开关 +### isPrecise +```java + /** + * 是否需要高精度计算 + */ + private boolean isPrecise = false; +``` + +> 高精度计算在会计财务中非常重要,java的float、double、int、long存在很多隐式转换,做四则运算和比较的时候其实存在非常多的安全隐患。 +> 所以类似汇金的系统中,会有很多BigDecimal转换代码。而使用QLExpress,你只要关注数学公式本身 _订单总价 = 单价 * 数量 + 首重价格 + ( 总重量 - 首重) * 续重单价_ ,然后设置这个属性即可,所有的中间运算过程都会保证不丢失精度。 + +### isShortCircuit + +```java + /** + * 是否使用逻辑短路特性 + */ + private boolean isShortCircuit = true; +``` +在很多业务决策系统中,往往需要对布尔条件表达式进行分析输出,普通的java运算一般会通过逻辑短路来减少性能的消耗。例如规则公式: +_star>10000 and shoptype in('tmall','juhuasuan') and price between (100,900)_ +假设第一个条件 _star>10000_ 不满足就停止运算。但业务系统却还是希望把后面的逻辑都能够运算一遍,并且输出中间过程,保证更快更好的做出决策。 + +### isTrace + +```java + /** + * 是否输出所有的跟踪信息,同时还需要log级别是DEBUG级别 + */ + private boolean isTrace = false; +``` +这个主要是是否输出脚本的编译解析过程,一般对于业务系统来说关闭之后会提高性能。 + +## 2、调用入参 + +```java +/** + * 执行一段文本 + * @param expressString 程序文本 + * @param context 执行上下文,可以扩展为包含ApplicationContext + * @param errorList 输出的错误信息List + * @param isCache 是否使用Cache中的指令集,建议为true + * @param isTrace 是否输出详细的执行指令信息,建议为false + * @param aLog 输出的log + * @return + * @throws Exception + */ + Object execute(String expressString, IExpressContext context,List errorList, boolean isCache, boolean isTrace, Log aLog); + +``` + +## 3、功能扩展API列表 +QLExpress主要通过子类实现Operator.java提供的以下方法来最简单的操作符定义,然后可以被通过addFunction或者addOperator的方式注入到ExpressRunner中。 + +```java + public abstract Object executeInner(Object[] list) throws Exception; + +``` + +比如我们几行代码就可以实现一个功能超级强大、非常好用的join操作符: + +_list = 1 join 2 join 3;_ -> [1,2,3] +_list = join(list,4,5,6);_ -> [1,2,3,4,5,6] + +```java +public class JoinOperator extends Operator{ + public Object executeInner(Object[] list) throws Exception { + java.util.List result = new java.util.ArrayList(); + Object opdata1 = list[0]; + if(opdata1 instanceof java.util.List){ + result.addAll((java.util.List)opdata1); + }else{ + result.add(opdata1); + } + for(int i=1;i[] aParameterClassTypes, + String errorInfo); +//fun(a,b,c) 绑定 Class.function(a,b,c)类方法 +void addFunctionOfClassMethod(String name, String aClassName, + String aFunctionName, Class[] aParameterClassTypes, + String errorInfo); +//给Class增加或者替换method,同时 支持a.fun(b) ,fun(a,b) 两种方法调用 +//比如扩展String.class的isBlank方法:“abc”.isBlank()和isBlank("abc")都可以调用 +void addFunctionAndClassMethod(String name,ClassbindingClass, OperatorBase op); + +``` + +### (2)Operator相关API + +提到脚本语言的操作符,优先级、运算的目数、覆盖原始的操作符(+,-,*,/等等)都是需要考虑的问题,QLExpress统统帮你搞定了。 + +```java + //添加操作符号,可以设置优先级 +void addOperator(String name,Operator op); +void addOperator(String name,String aRefOpername,Operator op); + + //替换操作符处理 +OperatorBase replaceOperator(String name,OperatorBase op); + + //添加操作符和关键字的别名,比如 if..then..else -> 如果。。那么。。否则。。 +void addOperatorWithAlias(String keyWordName, String realKeyWordName, + String errorInfo); + +``` + +### (3)宏定义相关API +QLExpress的宏定义比较简单,就是简单的用一个变量替换一段文本,和传统的函数替换有所区别。 + +```java +//比如addMacro("天猫卖家","userDO.userTag &1024 ==1024") +void addMacro(String macroName,String express) +``` + +### (4)java class的相关api +QLExpress可以通过给java类增加或者改写一些method和field,比如 链式调用:"list.join("1").join("2")",比如中文属性:"list.长度"。 + +```java +//添加类的属性字段 +void addClassField(String field,ClassbindingClass,ClassreturnType,Operator op); + +//添加类的方法 +void addClassMethod(String name,ClassbindingClass,OperatorBase op); +``` + +> 注意,这些类的字段和方法是执行器通过解析语法执行的,而不是通过字节码增强等技术,所以只在脚本运行期间生效,不会对jvm整体的运行产生任何影响,所以是绝对安全的。 + +### (4)语法树解析变量、函数的API + +> 这些接口主要是对一个脚本内容的静态分析,可以作为上下文创建的依据,也可以用于系统的业务处理。 +> 比如:计算 “a+fun1(a)+fun2(a+b)+c.getName()” +> 包含的变量:a,b,c +> 包含的函数:fun1,fun2 + +```java +//获取一个表达式需要的外部变量名称列表 +String[] getOutVarNames(String express); + +String[] getOutFunctionNames(String express); +``` + +### (5)语法解析校验api +脚本语法是否正确,可以通过ExpressRunner编译指令集的接口来完成。 +```java +String expressString = "for(i=0;i<10;i++){sum=i+1}return sum;"; +InstructionSet instructionSet = expressRunner.parseInstructionSet(expressString); +//如果调用过程不出现异常,指令集instructionSet就是可以被加载运行(execute)了! +``` + +### (6)指令集缓存相关的api +因为QLExpress对文本到指令集做了一个本地HashMap缓存,通常情况下一个设计合理的应用脚本数量应该是有限的,缓存是安全稳定的,但是也提供了一些接口进行管理。 +```java + //优先从本地指令集缓存获取指令集,没有的话生成并且缓存在本地 + InstructionSet getInstructionSetFromLocalCache(String expressString); + //清除缓存 + void clearExpressCache(); ``` \ No newline at end of file