返回悠然博客列表

TinyScript语言开源了

发布于 1月前

许多的人使用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]
]


 
相关信息
     标签
       附件
      文件 标题 创建者

      评分 0次评分

      日程