TinyScript语言介绍


声明:本文转载自https://my.oschina.net/tinyframework/blog/1525337,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

许多的人使用Java来作为主要的编程语言,许多的时候感觉代码太过繁复,当然有Scala、Kotlin、Python等等语言号称可以解决此问题,但是毕竟生态圈的切换不是个小问题。同时语法结构和Java相去甚远也导致切换的成本毕竟高。

为此本人做了一下尝试,准备走一个中间路线,主题还是用Java语言,但是在需要的时候用TinyScript来解决一下问题,然后再回到Java主体执行,所以你完全可以把它当成一种EL语言来使用,当然解决复杂问题也比常规的EL语言更方便,毕竟TinyScript在集合运算能力方面有重点扩展的地方。

未来的方向,会重点放在算法方面,目前已经内嵌了动态规划的背包问题通用方法,后面会逐步扩充其他算法,让程序员们不再纠结于算法实现,而是集中注意力在问题上。

语言特性列表

  • 支持有序数据结构:数组和序列
  • 支持无序数据结构:set和map
  • 支持专有数据结构:树和序表
  • 序表支持关联、匹配、过滤、分组、排序、聚会等多种业务运算
  • 与java无缝集成,适用于jdk1.6及以上版本
  • 支持new java对象,并可以使用Java所有类及对象
  • 可以采用obj.field方式访问和操作对象属性,简化obj.getField()和obj.setField(value);
  • 支持数据结构间相互转换
  • 支持调用java非静态方法和静态方法
  • 支持bean对象,可以操作bean对象的属性和方法
  • 可以和Spring集成,方便加载bean配置信息
  • 支持访问数据库,可以将表数据转换成序表结构
  • 支持访问Excel,可以将Sheet数据转换成序表结构
  • 支持访问文本,可以将行数据转换成序表结构
  • 支持不同数据源的序表操作,比如关联、匹配等
  • 支持object[key]扩展,比如访问list[1],map[key],简化用户操作
  • 支持object.field扩展,允许用户实现不同语法场景
  • 支持object.function(…)扩展,允许用户实现不同语法场景
  • 支持java的基本类型,内置不同精度的数值转换函数
  • 支持if/elseif/else、switch指令
  • 支持for、while循环指令
  • 支持基本表达式操作,符合java语法规范
  • 允许用户设置下标是否从0开始,方便用户访问元素
  • 支持[a .. b]方式生成指定范围的序列
  • 允许用户定制常量,可以在脚本引擎构造后直接使用,无需声明,如PI、E等。
  • 内置聚合函数和三角函数等系统函数,允许用户自行编写函数类进行扩展。
  • 允许用户编写脚本类,简化业务逻辑。
  • 允许用户编写脚本文件,同时支持java方式和IDE插件调用,实现即时开发测试。
  • 支持动态更新脚本文件,无需重新编译部署
  • 允许用户通过快速运行器执行脚本,也允许用户通过带Spring的运行器执行需要Spring环境的脚本
  • 定义了基本操作符,但是允许用户配置不同的对象实现重载。
  • 提供集合的差并交异或运算
  • 允许对集合子元素进行批量操作符运算,返回新的集合,如list*2
  • 允许对集合子元素进行批量方法运算,返回新的集合,如list.getName()
  • 允许对集合子元素进行批量属性运算,返回新的集合,如list.age
  • 支持lambda表达式,部分函数允许使用lambda表达式简化逻辑
  • 增强lambda特性,允许lambda变量修改外部同名变量。
  • 支持排列的lambda遍历操作
  • 支持组合的lambda遍历操作
  • 支持全排列的lambda遍历操作
  • 支持单方法接口的lambda封装,如Runnable、Comparator
  • 支持各种脚本内嵌执行,比如dataSource[[ sql语言 ]] 进行带@占位符的sql动态执行,支持template[[ 模板语言 ]] 进行模板语言执行,也可以继承各种其他脚本

当然上面列的不一定全,后面也会有新的语言特性加入。

脚本运行

脚本语言的扩展名是ts和tinyscript,当然也可以起其他的扩展名。

提供了Eclipse和Idea的执行器插件,安装之后可以右键直接运行脚本文件。

先推出看看反响如何,如果反响比较好,准备开发ide,支持高亮、调试等等。

配置Maven依赖

请在pom文件增加如下配置,注意tinyscript工程的版本一般选择最新正式版本

        <dependency>             <groupId>org.tinygroup</groupId>             <artifactId>org.tinygroup.tinyscript</artifactId>             <version>tinyscript正式版本号</version>         </dependency>

配置bean文件

本操作是可选项,如果使用者需要使用脚本(*.tinyscript)并且通过tiny文件扫描器加载,那么在bean文件配置如下信息:

<bean id="fileResolver" scope="singleton"           class="org.tinygroup.fileresolver.impl.FileResolverImpl">         <property name="fileProcessorList">             <list>                 .....                 <ref bean="scriptSegmentFileProcessor"/>             </list>         </property>     </bean>

scriptSegmentFileProcessor文件扫描器可以把脚本自动注册到脚本引擎。

Java方式运行

早期tinyscript没有提供IDE前端插件,只能通过Java接口调用验证

//不涉及调用脚本方法(无需注册脚本) ComputeEngine engine = new DefaultComputeEngine(); ScriptContext context  = new DefaultScriptContext();

以上代码实例化脚本引擎和上下文环境,如果注册脚本有两种方式:一种是配置bean文件扫描器,另一种是手动注册到脚本引擎

//涉及调用脚本方法(手动注册脚本) ComputeEngine engine = new DefaultComputeEngine(); ScriptContext context  = new DefaultScriptContext(); String content = FileUtil.readFileContent(new File("src/test/resources/multiresult.tinyscript"), "utf-8"); //本脚本路径仅做演示,用户需取实际地址 ScriptSegment scriptSegment = ScriptUtil.getDefault().createScriptSegment(engine, null, content); engine.addScriptSegment(scriptSegment);

如果执行上述代码没有抛出异常或输出异常日志,表示tinyscript初始化正常。

Eclipse插件

请参考:《Tiny模板运行器》,安装模板运行器插件到Eclipse上。该模板运行器也同样适用于脚本语言。

用户可以新建一个脚本文件,比如叫example.tinyscrpt 。请注意编码需要是utf-8,然后打开编辑器输入如下代码:

elements =[0,1,2,3,4,5,6,7,8,9]; elements.permute(3,(e) -> {     value = e[1]*100+e[2]*10+e[3];     if(pow(e[1],3)+pow(e[2],3)+pow(e[3],3)==value){       System.out.println(value);     } });

这段代码可以计算水仙花数,然后右键菜单"Run as"-"运行",命令窗口可以得到执行结果:

QQ截图20170707162256.png

通过这种方式,用户可以快速编辑、测试脚本代码。

基本类型

脚本支持Java的7种基本类型,包括boolean、char、short、int、long、float、double。

boolean型

最为简单,仅有true/false真假值定义。

脚本片段如下:

//boolean类型 println(true); println(false);

执行结果:

true false

char型

仅支持单字符,与java规范相同,支持特殊字符定义如\t,\n

脚本片段如下:

//char类型 println('1');  //单字符数值 println('a');  //单字符字母 println("start"+'\t'+"end");  //特殊字符:制表符

执行结果:

1 a start   end

int型

整型范围与java规范相同,支持二、八、十、十六进制的表示。+/-前缀表示正负数,正数可以省略前缀

脚本片段如下:

//int类型 println(123); //十进制的123 println(0110); //八进制,结果为72 println(0x6B7C); //十六进制,表示27516 println(0b111); //二进制,表示7 println(-123); //十进制的负数 println(-0110); //八进制的负数 println(-0x6B7C); //十六进制的负数 println(-0b111); //二进制的负数

执行结果:

123 72 27516 7 -123 -72 -27516 -7

long型

长整型范围与java规范相同,也支持二、八、十、十六进制的表示,但是需要用L/l做结尾。

脚本片段如下:

//long类型 println(99999999L); //十进制的99999999 println(0110L); //八进制,结果为72 println(0x6B7CL); //十六进制,表示27516 println(0b111L); //二进制,表示7 println(-99999999L); //十进制的负数 println(-0110L); //八进制的负数 println(-0x6B7CL); //十六进制的负数 println(-0b111L); //二进制的负数

执行结果:

99999999 72 27516 7 -99999999 -72 -27516 -7

float型

单精度浮点数遵守java规范,也是浮点数的默认类型。支持科学计数法。

脚本片段如下:

//float类型 println(1.1);    //默认浮点类型 println(1.1f);   //指定单精度类型 println(.1f);    //小于1的浮点数可以省略0前缀 println(9f);    println(1.0e3f);    //科学计数法表示浮点 println(0x7.5p8f);  //十六进制的浮点数 println(-2.3f);     //负单精度浮点数,正单精度浮点数可以省略前缀+

执行结果:

1.1 1.1 0.1 9.0 1000.0 1872.0 -2.3

double型

双精度浮点数遵守java规范,结尾需要用D/d做结尾

脚本片段如下:

//double类型 println(2.53D);    //双精度浮点数不能省略结尾D/d println(.53d);     //小于1的浮点数可以省略0前缀 println(10d); println(1.0e3d);    //科学计数法表示双精度浮点 println(-5.6d);     //负双精度浮点数,正双精度浮点数可以省略前缀+

执行结果:

2.53 0.53 10.0 1000.0 -5.6

String型

String表示字符串,虽然不是基本类型,因为使用场景非常多,所以一并列举。语法使用双引号包含字符串,如果字符串本身包含",需要使用\进行转义

脚本片段如下:

//String类型 println("abc");   //一般字符串 println("if");    //包含指令关键字的字符串 println("null");  //包含null关键字的字符串 a="cat"; d="dog"; op="and"; //引擎支持字符串嵌套变量,减少用户使用+拼接字符串 println("dog and ${b}");  //字符串的$渲染,语法符合找不到对象b,返回空值 println("dog and ${a}");  //字符串的$渲染,渲染a对象 println("${a}{}[]");      //字符串的$渲染,渲染a对象 println("mmmmm${a}");     //字符串的$渲染,渲染a对象 println("dog and ${1+2+3}");     //字符串的$渲染,支持表达式 println("##${a} ${op} ${b}##");  //渲染包含多个变量

执行结果:

abc if null dog and  dog and cat cat{}[] mmmmmcat dog and 6 ##cat and ##

null

空值,表示对象为空。注意null与"","null"是不一样的。

脚本片段如下:

//null println(a==null); //判断对象是否非空 println(b==null); //判断对象是否非空

执行结果:

false true

表达式运算

表达式由元素和操作符组成,元素一般可以分为基本类型和变量,基本类型如1,20000L,3.8d,false等,变量的话只是展示一个变量名,具体变量值存储在上下文。操作符种类很多如:四则运算操作符、逻辑运算符、移位符、三元表达式等等。本章节会逐一介绍。

1+2-3; //值运算 a+1-b;   //变量表达式运算,如果上下文不存在a、b就会出错

实际使用场景基于变量的表达式运算最为常见

整型的四则运算

// 基本的加、减、乘、除、求模、括号 println(1+2+3); println(1+2*3); println(100-5-50+177); println(10000/4/100+4); println(2343%5); println(1/2); println(-1/2); println(3*(2+2)-7); println((2+5)*(6-3)-7*2);

整型的四则运算与java相同,特别是注意整型除法,不是四舍五入,需要注意。

执行结果:

6 7 222 29 3 0 0 5 7

浮点数的四则运算

// 浮点的加、减、乘、除 println(1.0f+2.0d);      //结果3.0d println(1.0f-2.0d);      //结果-1.0d println(1.0d*2.5d-1.0d); println(1.0d/2);

浮点的四则运算优先级与整型一样

执行结果:

3.0 -1.0 1.5 0.5

逻辑运算:与、或、非、异或

脚本片段如下:

// 与、或、非、异或 println(!false); println(!true); println(~2); println(128&129); println(128|129); println(15^2);

执行结果:

true false -3 128 129 13

逻辑运算:短路操作

执行脚本片段如下:

println(1=='1'); println(1==0 && 1==1); println(1=="1"); println(1==0 || 1==1); println(1==1 || 6>7 || 8!=8 ); println(1==1 && a!=null && a.length()>8);  //测试与的短路操作 println(1==0 || a==null || a.length()>8);  //测试或的短路操作

执行结果:

false false true true true false true

逻辑运算:大小比较

执行脚本片段如下:

println(1==1); println(1==0); println(1!=0); println(0!=0); println(200>199); println(200>=200); println(200<199); println(200<=200);

执行结果:

true false true false true true false true

位移操作

执行脚本片段如下:

println(8>>2); println(8<<2); println(-121 >>> 4);

执行结果:

2 32 268435448

三元表达式

执行脚本片段如下:

println(1+1>=1?"yes":"no"); println(2==5-3?true:false); println(2!=5-3?true:false); a=10;b=20; println(a==20?'a':(b==10?'b':'c')); a=10;b=10; println(a==20?'a':(b==10?'b':'c')); a=20;b=20; println(a==20?'a':(b==10?'b':'c'));

执行结果:

yes true false c b a

高级示例

背包问题

//参数说明:list.DPknapsack(容量,重量,[每件物品的件数],价值,[规则])class Obj{    name,weight,value;    Obj(name,weight,value){    } } //==================================================================================================== //01背包list=[new Obj("a",2,6.0),new Obj("b",2,3.0),new Obj("c",6,5.0),new Obj("d",5,4.0),new Obj("e",4,6.0)]; println("01背包问题:\n"+list.dpKnapsack(10,list.weight,1,list.value)); //完全背包list=[new Obj("a",2,6.0),new Obj("b",2,3.0),new Obj("c",6,5.0),new Obj("d",5,4.0),new Obj("e",4,6.0)]; println("完全背包问题:\n"+list.dpKnapsack(10,list.weight,list.value)); //多重背包list=[new Obj("a",12,4.0),new Obj("b",2,2.0),new Obj("c",1,1.0),new Obj("d",4,10.0),new Obj("e",1,2.0)]; println("多重背包问题:\n"+list.dpKnapsack(15,list.weight,[1,7,12,3,1],list.value)); //混合背包list=[new Obj("a",12,4.0),new Obj("b",2,2.0),new Obj("c",1,1.0),new Obj("d",4,10.0),new Obj("e",1,2.0)]; println("多重背包问题:\n"+list.dpKnapsack(15,list.weight,[1,7,12,3,-1],list.value));

运行结果

01背包问题: [{result=15.0}, [Obj[name=a,weight=2,value=6.0], Obj[name=b,weight=2,value=3.0], Obj[name=e,weight=4,value=6.0]], [1, 1, 1]] 完全背包问题: [{result=30.0}, [Obj[name=a,weight=2,value=6.0]], [5]] 多重背包问题: [{result=34.0}, [Obj[name=b,weight=2,value=2.0], Obj[name=d,weight=4,value=10.0], Obj[name=e,weight=1,value=2.0]], [1, 3, 1]] 多重背包问题: [{result=36.0}, [Obj[name=d,weight=4,value=10.0], Obj[name=e,weight=1,value=2.0]], [3, 3]]

计算股票涨停

下面是某证券交易所一个月内的日收盘价记录,其中CODE列为股票代码,DT为日期,CL为收盘价。试找出这个月内曾连续三天涨停的股票。为避免四舍五入产生的误差,涨停的比率定为9.5%。

部分数据请见下图(完整记录请见附件stockRecords.txt,之后示例也遵守此规范)

QQ截图20170525112834.png

编写example1.tinyscript如下:

import org.tinygroup.etl.DataSet; import org.tinygroup.etl.Field; class Example1 {       /* 统计一个月内连续三天涨停的股票 */     countStock(path) {        ratio = 0.095d;        ds = readTxt(path);        groupds =ds.insertColumn(3,"UP").convert(CL,"double").group(CODE).sortGroup("DT ASC");        groupds.subGroup(1,1).update(UP,0d);  //每月的第一天涨停率为0        groupds.update(UP,(CL[0]-CL[-1])/CL[-1]);  //之后的每天统计当天的涨停率。        resultds = groupds.filterGroup(UP[0]>ratio && UP[1]>ratio && UP[2]>ratio);        return resultds;     } }

调用脚本的java代码:

public void testWithScript() throws Exception{    System.out.println("testWithScript is start!");    GroupDataSet groupDs = (GroupDataSet) engine.execute("m = new Example1(); return m.countStock(\"src/test/resources/StockRecords.txt\");", context);    //输出结果股票    for(int i=0;i<groupDs.getRows();i++){      System.out.println("code="+groupDs.getData(i+1,1));    }    System.out.println("testWithScript is end!"); }

结果如下:

testWithScript is start! code=201745 code=550766 code=600045 code=700071 testWithScript is end!

下面是某证券交易所一个月内的日收盘价记录,其中CODE列为股票代码,DT为日期,CL为收盘价。试找出这个月内曾连续三天涨停的股票。为避免四舍五入产生的误差,涨停的比率定为9.5%。

部分数据请见下图(完整记录请见附件stockRecords.txt,之后示例也遵守此规范)

QQ截图20170525112834.png

编写example1.tinyscript如下:

import org.tinygroup.etl.DataSet; import org.tinygroup.etl.Field; class Example1 {       /* 统计一个月内连续三天涨停的股票 */     countStock(path) {        ratio = 0.095d;        ds = readTxt(path);        groupds =ds.insertColumn(3,"UP").convert(CL,"double").group(CODE).sortGroup("DT ASC");        groupds.subGroup(1,1).update(UP,0d);  //每月的第一天涨停率为0        groupds.update(UP,(CL[0]-CL[-1])/CL[-1]);  //之后的每天统计当天的涨停率。        resultds = groupds.filterGroup(UP[0]>ratio && UP[1]>ratio && UP[2]>ratio);        return resultds;     } }

调用脚本的java代码:

public void testWithScript() throws Exception{    System.out.println("testWithScript is start!");    GroupDataSet groupDs = (GroupDataSet) engine.execute("m = new Example1(); return m.countStock(\"src/test/resources/StockRecords.txt\");", context);    //输出结果股票    for(int i=0;i<groupDs.getRows();i++){      System.out.println("code="+groupDs.getData(i+1,1));    }    System.out.println("testWithScript is end!"); }

结果如下:

testWithScript is start! code=201745 code=550766 code=600045 code=700071 testWithScript is end!

演示排序、组合和全排序

TinyScript提供了3种有关排列组合的函数,分别是permute(排列),combine(组合)和permuteAll(全排列)。让用户在有关数学运算方面使用起来更加方便。下面是这三个函数的有关使用的样例Permute:遍历数组所有数字的排列组合,每位数字都可重复。

使用方式: Permute(num,lambda)其中第一个参数代表输出排列数字的位数,第二个参数代表处理的匿名lambda表达式。也可以采用Permute(lambda)的形式,此时num默认为集合的数据个数。

样例:

elements = [0,1,2];  elements.permute(3,(record) -> {      println(record);  });

    blob.png

elements = [0,1,2]; elements.permute((record)->{ println(record); });

blob.png

来看一个更复杂的水仙花数的计算:

elements =[0,1,2,3,4,5,6,7,8,9]; elements.permute(3,(e) -> {     value = e[1]*100+e[2]*10+e[3];     if(pow(e[1],3)+pow(e[2],3)+pow(e[3],3)==value){       System.out.println(value);     } });

blob.png

permuteAll:该函数输出数组的全排列。数字不会重复。

使用方式:permuteAll(num,lambda),num表示全排列的数字个数,lambda是处理排列后数字的匿名函数。可以使用permuteAll(lambda),此时num默认为集合的数据个数。

样例:

elements = [0,1,2]; elements.permuteAll(2,(record)->{ println(record); });

blob.png

elements = [0,1,2];  elements.permuteAll((record) -> {  println(record);  });

blob.png

Combine:该函数输出数组的所有组合。

使用方式:combine(num,lambda),第一个参数num代表组合的位数,第二个参数代表处理每个组合数字的函数。也可以使用combine(lambda),此时num默认为集合的数据个数。

样例:

输出数组中3位一组合的所有组合形式:

elements = [0,1,2]; elements.combine(2,(record)->{ println(record); });

blob.png

elements = [0,1,2]; elements.combine((record)->{ println(record); });

blob.png

此外,还可以嵌套使用,比如我先求一个数组的所有组合再对组合结果进行全排列:

elements = [1,2,3,4];  elements.combine((record) -> {      if(record.size()==3){         record.permuteAll((e)->{             println(e);         });     } });

blob.png

序列的差并交异或

TinyScript支持对序列之间进行差并交异或的运算,具体使用方式如下。

序列的差(减去相同元素,若b在前面则结果是4):

a=[1,2,3]; b=[1,2,4]; println(a-b);

blob.png

或者用内置函数subtract,形如

a.subtract(b)

序列的相对差(交集的补集也就是异或):

a=[1,2,3]; b=[1,2,4]; println(a^b);

blob.png

或者用内置函数xor,形如

println(a.xor(b));

序列的并集:

a=[1,2,3]; b=[1,2,4]; println(a+b);

blob.png

或者用内置函数unite,例如:

println(a.unite(b));

序列的交集:

a=[1,2,3]; b=[1,2,4]; println(a&b);

blob.png

或者用内置函数intersect,例如:

println(a.intersect(b));

理财产品优化组合

class Product{    name,amountPerServing ,maxCount,rate;    //name:基金名字 amountPerServing:一份的价格 maxCount:最大份数 rate:利率Product(name,amountPerServing,maxCount,rate){    } } //============================================================================days=80; list=[ new Product("鹏华国防",100,10,0.00045), new Product("鹏华中证",100,20,0.00035), new Product("国投瑞银",100,20,0.00055), new Product("华商主题精选",100,10,0.0004), new Product("金鹰智慧",100,5,0.0003)]; println(list.dpKnapsack(5000,list.amountPerServing,list.maxCount,()->{    return [0.00045,0.00035,0.00055,0.00040,0.00030]*days*100;//每个基金的总收益由利率*时间*一份价格算出}));

运行结果:

[ {result=183.999998},  [Product[rate=4.5E-4,name=鹏华国防,maxCount=10,amountPerServing=100],  Product[rate=3.5E-4,name=鹏华中证,maxCount=20,amountPerServing=100],  Product[rate=5.5E-4,name=国投瑞银,maxCount=20,amountPerServing=100],  Product[rate=4.0E-4,name=华商主题精选,maxCount=10,amountPerServing=100]],  [10, 10, 20, 10] ]

本文发表于2017年08月29日 10:36
(c)注:本文转载自https://my.oschina.net/tinyframework/blog/1525337,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2054 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1