Aviator语法

Aviator语法

1 简介

Aviator 的基本过程是将表达式直接翻译成对应的 java 字节码执行,整个过程最多扫两次(开启执行优先模式,如果是编译优先模式下就一次),这样就保证了它的性能超越绝大部分解释性的表达式引擎,测试也证明如此;其次,除了依赖 commons-beanutils 这个库之外(用于做反射)不依赖任何第三方库,因此整体非常轻量级,整个 jar 包大小哪怕发展到现在 5.0 这个大版本,也才 430K。同时, Aviator 内置的函数库非常“节制”,除了必须的字符串处理、数学函数和集合处理之外,类似文件 IO、网络等等你都是没法使用的,这样能保证运行期的安全,如果你需要这些高阶能力,可以通过开放的自定义函数来接入。因此总结它的特点是:

  • 高性能

  • 轻量级

  • 一些比较有特色的特点:

    • 支持运算符重载

    • 原生支持大整数和 BigDecimal 类型及运算,并且通过运算符重载和一般数字类型保持一致的运算方式。

    • 原生支持正则表达式类型及匹配运算符 =~

    • 类 clojure 的 seq 库及 lambda 支持,可以灵活地处理各种集合

  • 开放能力:包括自定义函数接入以及各种定制选项

2 基本语法

2.1 基本类型及运算

Aviator 支持常见的类型,如数字、布尔值、字符串等等,同时将大整数、BigDecimal、正则表达式也作为一种基本类型来支持

数字

数字包括整数、大整数、浮点数和BigDecimal,均可以使用 +,-,*,/ 来做算术运算,取模运算符就是 %

数字类型在运算的时候,会遵循一定的类型转换规则:

  • 单一类型参与的运算,结果仍然为该类型,比如整数和整数相除仍然是整数,double 和 double 运算结果还是 double。

  • 多种类型参与的运算,按照下列顺序: long -> bigint -> decimal -> double 自动提升,比如 long 和 bigint 运算结果为 bigint, long 和 decimal 运算结果为 decimal,任何类型和 double 一起运算结果为 double

你可以通过 long(x) 将一个数字强制转化为 long,这个过程中可能丢失精度,也可以用 double(x) 将一个数字强转为 double 类型

** ,原来使用 math.pow(a, b) 的代码都可以写成 a**b幂运算符的优先级较高,在单目运算符之上;

除此之外,整数还支持位运算

完整的运算符优先级的优先顺序如下表,基本跟 java 保持一致,除了特别引入的正则匹配

优先级运算符结合性
1( ) [ ]  .从左到右
2**从左到右
3!  ~从右到左
4*  /  %从左到右
5+  -从左到右
6<<  >>  >>>从左到右
7<  <=  >  >=从左到右
8==  !=从左到右
9&从左到右
10^从左到右
11|从左到右
12&&从左到右
13||从左到右
14? :从左到右
15= =~从右到左

记住这个优先级没有太大必要,无论如何,都推荐使用括号来明确表示优先级

重载运算符

前面我们已经看了运算符重载的例子,比如加号 + 可以用于数字,也可以用于拼接字符串,这就是一个重载。你也可以自定义任意运算符的行为,比如我们想将整数相除的结果修改为浮点数,通过 AviatorEvaluatorInstance#addOpFunction(opType, function) 就可以自定义运算符的行为,如下:

package com.googlecode.aviator.example;
​
import java.util.Map;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.lexer.token.OperatorType;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorDouble;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorType;
​
/**
 * An example to demo custom division operator.
 *
 * @author dennis(killme2008@gmail.com)
 *
 */
public class CustomDivideExample {
  public static void main(final String[] args) {
    AviatorEvaluator.getInstance().addOpFunction(OperatorType.DIV, new AbstractFunction() {
​
      @Override
      public AviatorObject call(final Map<String, Object> env, final AviatorObject arg1,
          final AviatorObject arg2) {
        if (arg1.getAviatorType() == AviatorType.Long
            && arg2.getAviatorType() == AviatorType.Long) {
          // If arg1 and arg2 are all long type.
          // Cast arg2 into double and divided by arg1.
          double d = FunctionUtils.getNumberValue(arg1, env).longValue()
              / FunctionUtils.getNumberValue(arg2, env).doubleValue();
          return AviatorDouble.valueOf(d);
        } else {
          // Otherwise, call aviatorscript's div function.
          return arg1.div(arg2, env);
        }
      }
​
      @Override
      public String getName() {
        return OperatorType.DIV.getToken();
      }
    });
​
    System.out.println(AviatorEvaluator.execute("1/2"));
  }
}
​

字符串

Aviator 中同样支持字符串,只要以单引号或者双引号括起来的连续字符就是一个完整的字符串对象,例如:

  • "hello world"'hello world'

字符串的长度可以通过 string.length 函数获取:

字符串拼接可以用 + 号(这又是一个运算符重载),任何类型和字符串相加,都将拼接为字符串,这跟 java 的规则一致。字符串还包括其他函数,如截取字符串 substring,具体见函数库列表

转义

和 java 语言一样,通过 \ 来转义一个字符,比如我们想表示的字符串中有单引号,如果我们继续使用单引号来表示字符串,这时候就需要用到转义符,特殊字符,比如 \r\n\t 等也是同样支持

字符串插值

字符串拼接可以使用 + 号,但有些复杂的场景过于丑陋,Aviator 支持了字符串插值

字符串中 #{} 括起来的表达式都将在当前上下文里自动执行求值,然后插入到最终的结果字符串

布尔类型和逻辑运算

布尔类型用于表示真和假,它只有两个值 truefalse 分别表示真值和假值。

比较运算如大于、小于可以产生布尔值,所有的逻辑运算符如下:

  • > 大于

  • >= 大于等于

  • < 小于

  • <= 小于等于

  • == 等于

  • != 不等于

布尔值可参于逻辑与、逻辑或、逻辑否等运算,假设 xy 的返回结果是布尔值:

  • x && y 表示并且的关系,x 为真,并且 y 为真的情况下,结果为 true,否则 false。

  • x || y 表示或者的关系, x 为真,或者 y 为真,结果就为 true,两者都为假值的时候结果为 false。

  • !x 否定运算符,如果 x 为 true,则结果为 false,反之则为 true。

&&|| 都支持短路规则

  • 如果 x 为假值, x && y 直接返回 false, y 就不进行求值。

  • 如果 x 为真值, x || y 直接返回 true, y 也不进行求值。

三元运算符

布尔值可用于三元表达式和条件语句作为判断,决定执行哪个分支代码。这里先介绍下三元表达式 test ? stmt1: stmt2 ,当 test 的执行结果为 true 的时候,执行 stmt1 ,反之则执行 stmt2 ,结果即为 stmt1 或者 stmt2 的执行结果

跟 java 不同的是 Aviator 允许两个分支返回的结果类型可以是不兼容的,如:

a > b ? "a > b" : 999 两个分支的结果分别是字符串和数字,这在 Aviator 中是完全可以的,这里将返回字符串 a > b

正则表达式

Aviator中正则表达式也是一等公民,作为基本类型来支持, / 括起来的正则表达式就是一个 java.util.Pattern 实例,例如 /\d+/ 表示 1 个或者多个数字,正则表达式语法和 java 完全相同,但是对于需要转义的字符不需要连续的反斜杠 \\ ,只要一个 \ 即可,比如我们要匹配 .av 为结尾的文件,正则可以写成 /^.*\.av$/ ,这里的 \. 来转义后缀里的 . 符号。

正则表达式能参与的运算只有比较运算符和正则匹配运算符 =~ :

## examples/regexp.av
​
let p = /^(.*)\.av$/;
​
println(p == p); ## print true
​
println("regexp.av" =~ p); ##print true
​
println("$0=" + $0);
println("$1=" + $1);

我们定义了一个正则表达式 p,用于匹配以 .av 结尾的文件名,匹配是用 =~ 运算符,匹配运算符左侧是字符串,右侧是正则表达式,如果匹配成功,返回 true,否则返回 false。

这里 regexp.av 是匹配成功,因此打印 true , 如果匹配成功,同时 Aviator 会将正则中的匹配的分组放入 $0$1 , $2 ... 的变量中,其中 $0 表示匹配的整个字符串,而 $1 表示第一个分组,以此类推。这里就是文件名,正则中用括号括起来的第一个分组 (.*)

2.2 变量

定义和赋值

和其他语言类似, Aviator 也允许定义变量,变量有特定的类型和“指向”的值。不过 Aviator 中变量的定义和赋值是不可分割的,定义一个变量的同时需要给他赋值

## examples/type.av
​
a = 1;
println(type(a));
​
s = "Hello AviatorScript";
println(type(s));
​
let p = /(?i)hello\s+(.*)/;
println(type(p));
​
if s =~ p {
  println($1);
}

我们将变量 a 定义并赋值成了整数 1,将变量 s 定义并赋值为字符串 Hello AviatorScript ,将变量 p 赋值为一个正则表达式,接下来你就可以正常使用这些变量来参与各种运算,比如上面的例子做了正则匹配,提取 hello 后面的字符串。

在这个例子中,我们还使用 type(x) 函数来获取 x 的类型,它会返回代表具体类型的字符串,上述代码执行将打印:

long
string
pattern
AviatorScript

动态类型:s 原来是一个字符串,我们通过赋值 s = 99 ,他的类型变为了数字,就可以参与算术运算。

nil: 当我们想表示一个变量还没有赋值的时候,需要用到 nil 这个特殊类型,它和 java 中的 null 作用一样,表示当前变量还没有赋值。nil 最常见的场景就是用于判断变量是否为 null:

传入变量

Aviator 是一个表达式引擎,因此是允许执行的时候传入变量

String expression = "a-(b-c) > 100";
Expression compiledExp = AviatorEvaluator.compile(expression);
// Execute with injected variables.
Boolean result =
      (Boolean) compiledExp.execute(compiledExp.newEnv("a", 100.3, "b", 45, "c", -199.100));
System.out.println(result);

我们通过 execute(env) 中的 env 来为表达式注入变量,传入的变量的作用域都是默认的脚本内的全局作用域

如果脚本中用到的变量没有传入,并且没有定义,那么默认值将是 nil

2.3 作用域

let 语句

let 语句就是让你在特定作用域内定义一个变量,如果父作用域有同名的变量,将“掩盖”父作用域的变量。如果不使用 let ,你读写的将仍然是父作用域的变量:

作用域还可以继续深层嵌套,遵循的规则不变:

  1. let 定义当前作用域的变量,这些变量同时可以被它的子作用域访问和修改,离开当前作用域后不可触达。

  2. let 定义的变量将“掩盖”父作用域的同名变量。

  3. 子作用域可以访问和修改父作用域定义的变量,离开子作用域后修改继续生效。

## examples/scope3.av
​
## examples/scope3.av
​
let a = 1;
{
  a = 2;
  let a = 3;
  b = 4;
  {
    a = 5;
    b = 6;
    let c = 7;
    println("a in scope2:" + a);
    println("b in scope2:" + b);
    println("c in scope2:" + c);
  }
  println("a in scope1:" + a);
  println("b in scope1:" + b);
  println("c in scope1:" + c);
}
​
println("a in global scope:" + a);
println("b in global scope:" + b);
​

输出

a in scope2:5
b in scope2:6
c in scope2:7
a in scope1:5
b in scope1:6
c in scope1:null
a in global scope:2
b in global scope:null
c in global scope:null

2.4 多行表达式和 return

Aviator支持多行表达式,表达式之间必须以分号 ; 隔开,支持换行,我们前面已经见了很多例子了

整个脚本的返回结果默认是最后一个表达式的结果。但是这里需要注意的是,加上分号后,整个表达式的结果将固定为 nil,因此如果你执行"c = a + b;",并打印结果,一定是 null,而不是 c 的结果。如果你想返回表达式的值,而不是为 nil,最后一个表达式不加分号即可

在 Aviator 中任何表达式都有一个值,加上分号后就是丢弃该值固定为 nil。

除了不加分号来返回之外,你也可以用 return 语句来指定返回:"c = a + b;return c;" 注意, return 语句就必须加上分号才是完整的一条语句,否则将报语法错误。return 也用于提前返回,结合条件语句可以做更复杂的逻辑判断:

## examples/if_return.av
​
if a < b {
  return "a is less than b.";
}
​
return a - b;

2.5 创建对象和 new

Aviator默认支持 new 语句(启用 Feature.NewInstance 特性),可以用于创建任意的 java 对象:

## examples/new.av
​
let d = new java.util.Date();
​
p(type(d));
p(d);
​
let year = getYear(d);
let month = getMonth(d);
​
p("Year is: " + year);
p("Month is: " + month);

输出

java.util.Date
Thu Apr 23 11:25:52 CST 2020
Year is: 120
Month is: 3

**java.lang** 下的类,都需要加上完整的包名前缀。

如果在启用了反射机制的情况下(默认 aviator 脚本模式下启用),你还可以调用任意方法:

USE

aviator支持 use 语句,类似 java 里的 import 语句,可以导入 java 类到当前命名空间,减少在 new 或者 try...catch 等语句里写完整包名的累赘方式。 use 语句的使用方式多种,最简单的情况是导入单个 Java 类:use java.util.Date;

如果要导入某个包下面的任意类,可以用通配符 * :use java.util.*;

如果你只是想引入包下的数个类,而不是全部,可以通过 use 包名.{类1, 类2...} 的方式,看一个更复杂的例子

use java.util.concurrent.locks.{ReentrantLock, ReentrantReadWriteLock};
use java.util.concurrent.CountDownLatch;

let x = 0;
let n = 10;

let lk = new ReentrantLock();

let latch = new CountDownLatch(n);

for i in range(0, n) {
  let t = new Thread(lambda() ->
                     lock(lk);
                     x = x + 1;
                     unlock(lk);
                     countDown(latch);
                     p("thread #{i} done");
                     end);
  start(t);
}

await(latch);
p("x=#{x}");


let lk = new ReentrantReadWriteLock();
let wlk = writeLock(lk);
lock(wlk);
x = x + 1;
unlock(wlk);

p("x=#{x}");

我们使用 ReentranLock 来保护变量 x ,并且使用 CountDownLatch 来同步所有线程执行完成。接下来我们用 ReentrantReadWriteLock 读写锁来保护 x 。可以看到 use java.util.concurrent.locks.{ReentrantLock, ReentrantReadWriteLock}; 这一行代码导入了两个 Lock 类。

执行输出:

thread 4 done
thread 5 done
thread 2 done
thread 0 done
thread 3 done
thread 1 done
thread 6 done
thread 7 done
thread 8 done
thread 9 done
x=10
x=11
null

不建议使用,如果需要创建对象可以借助自定义函数

3 进阶语法

3.1 条件语句

if 接受一个布尔表达式,如果其值为 true 就执行后续的代码块。如果为 false ,可以带上 else 语句执行其中的代码块,代码块都是以大括号包起来:

请注意,代码块都必须用大括号包起来,哪怕是单行语句,这跟 java 是不一样的

嵌套结构,但是更应该直接使用 elsif 语句,类似 Java 中的 else if ,比如我们写一个猜数字的例子

## examples/if.av
let a = rand(1100);

if a > 1000 {
  println("a is greater than 1000.");
} elsif a > 100 {
  println("a is greater than 100.");
} elsif a > 10 {
   println("a is greater than 10.");
} else {
   println("a is less than 10 ");
}

println("a is " + a + ".");

比如上面的代码写生成一个 [0, 1100) 内的整数,然后通过一系列条件语句,判断 a 的范围,并打印相应的字符串,最后打印 a 的值是多少。同样 elsif 对应的判断语句的括号也是可以忽略的。

3.2 循环语句

循环语句通常用于遍历一个集合,或者重复执行若干指令,直到满足某个条件等等。 Aviator 支持 forwhile 两种循环语句,分别介绍如下。

for 语句

遍历集合

for ... in 语句通常用于遍历一个集合,例如下面是遍历 0 到 9 的数字,并打印:

## examples/for_range1.av

for i in range(0, 10) {
  println(i);
}

其中 range(start, end) 函数用于创建一个 [start, end) 区间的整数集合,在迭代过程中,将 i 绑定到集合中的每个元素上,然后执行 {...} 里的代码块。大括号也是必需的,不能因为代码块是单行语句而忽略,这跟 java 是不同的。

for .. in 可以用于任何集合结构,比如数组、 java.util.Listjava.util.Map 等等:

## examples/for_seq.av

let m = seq.map("a", 1, "b", 2, "c", 3);

for x in m {
  println(x.key + "=" + x.value);
}

let list = seq.list(1, 2, 3, 4, 5, 6, 7, 8, 9);

let sum = 0;
for x in list {
  sum = sum + x;
}

println("sum of list is "+ sum);

这里 m 是一个 HashMap ,通过 seq.map 函数创建,里面是三个键值对 a=1, b=2, c=3 ,我们也可以通过 for...in 语句来遍历,并且通过 x.keyx.value 来访问每一对的键值

list 就是一个 1 到 9 整数组成的 List ,我们利用 for 语句迭代累计它们的和 sum 并打印

索引和 KV 遍历

for 语句还支持迭代遍历过程带上索引或者 key/value:

如果你想在执行代码块中途跳过剩余代码,继续下个迭代,可以用 continue,同样,如果想中途跳出迭代,你可以用 break:

while 语句

while 循环本质上是条件语句和循环的结合,当满足一定条件下,不停地执行一段代码块,直到条件变为否定, while 也可以用 break 和 continue 语句

## examples/while2.av

let sum = 1;

while true {
  sum = sum + sum;
  if sum > 1000 {
    break;
  }
}

println(sum);

使用 while true 来无限循环,直到 sum 大于 1000 就 break 跳出。

3.3 Statement 语句和值

在 Aviator 中,每个语句都有一个值,而不仅仅是整个脚本(参见 小节多行表达式和 return)。比如 if 条件语句也有一个值,就是实际执行的分支语句的结果

如条件语句和循环语句都可赋值给变量,下面主要介绍块(Block)的赋值

let c = {
  let a = 1;
  let b = 2;
  
  if a > b {
    return a;
  } else {
    return b;
  }
};

p("c is :" + type(c) +", " + c);

输出 c is :long, 2

3.4 异常处理

Aviator 完整支持了 java 的异常处理机制,只是做了一些简化:

 ## examples/handle_exception.av
 
 try {
	throw "an exception";
 } catch(e) {
	pst(e);
 } finally {
  p("finally");
 }

这段代码有几个特点:

  1. throw 抛出了一个字符串,在 Aviator 中,可以 throw 任何东西,非异常的对象都将被转化包装为标准错误 com.googlecode.aviator.exception.StandardError 类的实例。

  2. catch(e) 没有指定异常的类型, Aviator 允许不指定异常类型,等价于 catch(Throwable e)

  3. pst(e) 用于打印异常堆栈,也就是 e.printStackTrace() 调用。

  4. Aviator 中同样支持 finally 语句,这跟 Java 保持一致。

3.5 函数和闭包

函数

我们通过 fn 语法来定义一个命名函数:

## examples/function.av
​
fn add(x, y) {
  return x + y;
}
​
three = add(1, 2);
println(three);##3
s = add('hello', ' world');
println(s);##hello world

同样, 从 5.2 版本开始,aviator也支持了不定参数个数的函数定义,跟 java 的要求类似,也要求可变参数只能出现在参数列表的最后一个位置,并且用 & 作为前缀

fn average(&args) {
    return sum(*args) / count(args);
}
​
fn sum(&args) {
    s = 0.0;
    for arg in args {
        s = s + arg;
    }
    return s;
}
​
p(average(1, 2, 3, 4));

sum 接受一个可变参数数组,因此在 average 调用 sum 的时候,需要 unpacking解包,只要给 list 前面加上 * 号,也就是 *list 就可以自动帮你“展开”,否则 sum 接收到的是一个数组组成的数组。上述结果为 2.5

匿名函数 lambda

你可以通过 lambda 语法定义一个匿名函数:

## examples/lambda.av
​
let three = (lambda (x,y) -> x + y end);
println(three(1, 2));

lambda (x,y) -> x + y end 定义了一个匿名函数,接受参数 x 和 y,返回两者的和。定义形式lambda (参数1,参数2...) -> 参数体表达式 end

在 Aviator 中,函数也是一种类型,可以作为参数来传递,可以作为函数的返回值等等。

## examples/function_first_class.av
​
fn square(x) {
  return x * 2;
}
​
let add = lambda(x, y, f) ->
  f(x) + f(y)
end;
​
let add_n = lambda(x) ->
  lambda(y) -> 
    x + y
  end
end;
​
println(type(square));
println(type(add));
println(type(add_n));
​

这里的 add 函数接受的第三个参数 f 也是一个函数,我们用它调用 x 和 y,然后再相加,可见函数是可以作为参数来使用

3.6 自定义函数

如果你想在 Aviator 中调用 Java 方法,除了内置的函数库之外,你还可以通过下列方式来实现:

  1. 自定义函数

  2. 自动导入 java 类方法

  3. FunctionMissing 机制

可以通过 java 代码实现并往引擎中注入自定义函数,在 Aviator 中就可以使用,事实上所有的内置函数也是通过同样的方式实现的,所有的函数都实现了 AviatorFunction 接口

public class TestAviator {
    public static void main(String[] args) {
            //注册函数
            AviatorEvaluator.addFunction(new AddFunction());
            System.out.println(AviatorEvaluator.execute("add(1, 2)"));           // 3.0
            System.out.println(AviatorEvaluator.execute("add(add(1, 2), 100)")); // 103.0
        }
    }
    class AddFunction extends AbstractFunction {
        @Override
        public AviatorObject call(Map<String, Object> env, 
                                  AviatorObject arg1, AviatorObject arg2) {
            Number left = FunctionUtils.getNumberValue(arg1, env);
            Number right = FunctionUtils.getNumberValue(arg2, env);
            return new AviatorDouble(left.doubleValue() + right.doubleValue());
        }
        public String getName() {
            return "add";
        }
    }
}

它有一系列 call 方法,根据参数个数不同而重载,其中 getName() 返回方法名。一般来说都推荐继承 com.googlecode.aviator.runtime.function.AbstractFunction ,并覆写对应参数个数的方法即可,例如上面例子中定义了 add 方法,它接受两个参数

这个实现中是将两个参数相加,返回浮点结果 AviatorDouble

注意,哪怕结果为 null,也必须返回 **AviatorNil.NIL** 表示结果为 null,而不是直接返回 java 的 null。

要实现可变参数的函数,如果是直接继承 AbstractFunction ,要实现一系列的 call 方法,未免太繁琐了,因此 Aviator 还提供了 AbstractVariadicFunction ,可以更方便地实现可变参数函数,比如内置的 tuple(x, y, z,...) 创建 Object 数组的函数就是基于它实现:

除了用 java 代码实现自定义函数之外,你也可以使用 lambda 来定义,比如上面 add 的例子可以修改为:

AviatorEvaluator.defineFunction("add", "lambda (x,y) -> x + y end");
AviatorEvaluator.execute("add(1,2)"); // 结果为 3

当然,这个例子跟上面的 add 实现还是稍有区别的,这里并没有限定 x, y 必须为数字,并且返回类型也没有限定为 double。因此它也可以将字符串相加

3.7 使用Java方法

导入方法

假设你有一个工具类 StringUtils ,里面有一系列 public 的静态方法,如 StringUtils.isBlank 等,那么通过:

AviatorEvaluator.addStaticFunctions("str", StringUtils.class);//静态方法
AviatorEvaluator.addInstanceFunctions("s", String.class);//实例方法
str.isBlank('')
s.indexOf("hello", "l")

Function Missing

Function Missing 是类似 Ruby 的 method missing 机制,本质上是一个函数调用的兜底机制,当函数找不到的时候,就调用到 FunctionMissing 接口的实现:

public class FunctionMissingExample {

  private static class TestFunctionMissing implements FunctionMissing {

    @Override
    public AviatorObject onFunctionMissing(final String name, final Map<String, Object> env,
        final AviatorObject... args) {
      // Returns the function name.
      System.out.println(
          "Function not found, name=" + name + ", env=" + env + ", args=" + Arrays.toString(args));
      return FunctionUtils.wrapReturn(name);
    }

  }

  public static void main(final String[] args) {
    // Set function missing handler.
    AviatorEvaluator.setFunctionMissing(new TestFunctionMissing());

    System.out.println(AviatorEvaluator.execute("test(1,2,3)"));
    System.out.println(AviatorEvaluator.execute("not_found(1,2,3)"));
  }
}

以上方式会用到反射,与自定义函数相比性能较差(JDK 8 上大概有 3 倍左右的差距),推荐使用自定义函数

3.8 数组和集合

数组

tuple 函数可以创建一个固定大小的数组,等价 java 的类型为 Object []

可以通过 seq.array(type, ..args) 可以创建 type 类型的数组:

let s = seq.array(java.lang.String, "hello", "world", "aviator");
​
println(string.join(s, ","));

string.join 函数将第一个参数的字符串集合用第二个参数的字符串起来,这里将输出 hello,world,aviator

创建多维数组

seq.array_of(Class, &dimensions) 也可以用于创建多维数组,举例来说:

​
## create multidimensional array
​
let a = seq.array_of(long, 3, 2);
​
assert(3 == count(a));
assert(2 == count(a[0]));
​
let x = 0;
for i in range(0, 3) {
  for j in range(0, 2) {
     a[i][j] = x;
     x = x + 1;
  }
}
​
for i in range(0, 3) {
  for j in range(0, 2) {
    p("a[#{i}][#{j}] = #{a[i][j]}");
  }
}

我们创建了一个 2 x 3 的二维数组,并遍历初始化,最终打印数组:

a[0][0] = 0
a[0][1] = 1
a[1][0] = 2
a[1][1] = 3
a[2][0] = 4
a[2][1] = 5

集合 List, Map 和 Set

  • 创建一个链表可以通过 seq.list 函数

  • 创建一个 HashMap 也很容易,使用 seq.map(k1, v1, k2, v2 ...) 的方式,如果要获取 key 的集合,可以用 seq.keys(m) 函数, value 集合是用 seq.vals 函数

  • 创建不重复的元素组成的集合 Set,可以用 seq.set

参数为空则为空集合

操作集合

这里我们介绍操作这些集合类的通用操作

  • 添加元素 seq.add(coll, element)

  • 访问元素 seq.get(coll, key)

  • 判断元素是否存在:对于数组、List 和 Set 来说,判断某个元素是否存在都应该用 include(coll, element) 函数,对于 map 来说,如果是判断 key 是否存在,需要用 seq.contains_key(coll, key)

  • 遍历集合和数组的方式一样,同样通过 for..in 语句:

  • 删除元素 seq.remove(coll, element)

Sequence

Sequence 是 Aviator 对“集合”的抽象。这个“集合”囊括了数组、Set/Map/List 等等,只要它是是可遍历的集合即可。对于 Sequence 的抽象, Aviator 也提供了一套高阶函数来方便地对集合做转换、过滤、查询以及聚合,我们将一一介绍。这些函数的规则都是将 sequence 作为第一个参数。

count

count(seq) 函数用于获取 seq 里的集合元素之和

is_empty

is_empty 用于返回集合是否为空, is_empty(nil) 返回 true

map

map(seq, fn) 用于将 seq 转换另一个 seq,它将第二个参数 fn 的函数作用在集合里的每个元素上,结果收集到另一个集合

into

into(to_seq, from_seq) 用于将 from_seq 的元素,逐一添加到 to_seq 集合

reduce

reduce(seq, fn, init) 用于“聚合” seq 中的元素,第一次迭代的时候,它将调用第二个参数的函数结合第三个参数初始值 fn(init, element) 作用在每个元素 element 上,返回的结果在后续迭代中继续调用 fn(result, element), reduce 调用等价于下面的代码:

fn reduce(seq, fn, init) {
  let result = init;
  for element in seq {
    result = fn(result, element);
  }
  return result;
}

sort

sort(seq) 仅用于排序数组或者 List,其他 seq 类型无效,其他集合类型需要通过 into 等函数转换成 List 才可以使用

filter

filter(seq, fn) 用于过滤一个 seq,它将 fn 函数作用在每个元素上,结果返回 true 的收集到新 seq,否则就丢掉

seq.every和seq.not_any

seq.every(seq, fn) , 用于检查 seq 里的元素是否都满足 fn(x) == true ,如果都满足,返回 true ,否则是 false;

seq.not_any(seq, fn)seq.every 正好相反, 当且仅当 seq 里的每个元素满足 fn(x) == false 才返回 true

seq.some

seq.some(seq, fn) 返回 seq 中第一个使得 fn(x) == true 的元素,如果没有找到,返回 nil

take_while和drop_while

take_while(sequence, pred) 用于从集合 sequence 里挑选出 pred(元素) 返回 true 的元素并返回新的集合

fn is_neg(x) {
  x < 0
}
​
let list = seq.list(-2, -1, 0, 1, 2, 3, 0, 99, -1000, 7);
let result = take_while(list, is_neg);
p("result of take_while: #{result}");
//result of take_while: [-2, -1, -1000]

drop_while是将pred(元素) drop 出去,剩下的元素返回新的集合

group_by

group_by(sequence, keyfn) 可以为集合做分组,它会将 keyfn 函数作用到集合里的每个元素上,返回分组的 key,然后返回相同 key 的将放在同一个 list 里,最终返回一个 map 映射: {key1 -> [e1, e2], key2 -> [e3, e4], ...}

distinct

distinct(sequence) 用于消除集合中的重复元素,返回没有重复的集合

reverse

reverse(sequence) 用于返回集合的逆序结果,仅可作用于数组、List 等顺序集合

zipmap

接下来我们将描述几个用于产生集合的函数,先从 zipmap(list1, list2) 开始,它是将两个集合按照 e1-> e2 的顺序映射成一个 map

concat

concat(seq1, seq2) 用于连接两个集合,生成一个新的集合

自定义 sequence

假设有这么一个场景,你从数据库查询 User 表拿到了一个 java.sql.ResultSet ,你想传入 Aviator 处理,并且想使用上面提到的各种函数,那么你可以为 ResultSet 实现一个 seq 包装

4 函数列表

4.1 系统函数

函数名称说明
assert(predicate, [msg])断言函数,当 predicate 的结果为 false 的时候抛出 AssertFailed 异常, msg 错误信息可选。
sysdate()返回当前日期对象 java.util.Date
rand()返回一个介于 [0, 1) 的随机数,结果为 double 类型
rand(n)返回一个介于 [0, n) 的随机数,结果为 long 类型
cmp(x, y)比较 x 和 y 大小,返回整数,0 表示相等, 1 表达式 x > y,负数则 x < y。
print([out],obj)打印对象,如果指定 out 输出流,向 out 打印, 默认输出到标准输出
println([out],obj) 或者 p([out], obj)与 print 类似,但是在输出后换行
pst([out], e);等价于 e.printStackTrace(),打印异常堆栈,out 是可选的输出流,默认是标准错误输出
now()返回 System.currentTimeMillis() 调用值
long(v)将值转为 long 类型
double(v)将值转为 double 类型
boolean(v)将值的类型转为 boolean,除了 nil 和 false,其他都值都将转为布尔值 true。
str(v)将值转为 string 类型,如果是 nil(或者 java null),会转成字符串 'null'
bigint(x)将值转为 bigint 类型
decimal(x)将值转为 decimal 类型
identity(v)返回参数 v 自身,用于跟 seq 库的高阶函数配合使用。
type(x)返回参数 x 的类型,结果为字符串,如 string, long, double, bigint, decimal, function 等。Java 类则返回完整类名。
is_a(x, class)当 x 是类 class 的一个实例的时候,返回 true,例如 is_a("a", String) ,class 是类名。
is_def(x)返回变量 x 是否已定义(包括定义为 nil),结果为布尔值
undef(x)“遗忘”变量 x,如果变量 x 已经定义,将取消定义。
range(start, end, [step])创建一个范围,start 到 end 之间的整数范围,不包括 end, step 指定递增或者递减步幅。
tuple(x1, x2, ...)创建一个 Object 数组,元素即为传入的参数列表。
eval(script, [bindings], [cached])对一段脚本文本 script 进行求值,等价于 AviatorEvaluator.execute(script, env, cached)
comparator(pred)将一个谓词(返回布尔值)转化为 java.util.Comparator 对象,通常用于 sort 函数。
max(x1, x2, x3, ...)取所有参数中的最大值,比较规则遵循逻辑运算符规则。
min(x1, x2, x3, ...)取所有参数中的最小值,比较规则遵循逻辑运算符规则。
constantly(x)用于生成一个函数,它对任意(个数)参数的调用结果 x。

4.2 字符串函数

函数名称说明
date_to_string(date,format)将 Date 对象转化化特定格式的字符串,2.1.1 新增
string_to_date(source,format)将特定格式的字符串转化为 Date 对 象,2.1.1 新增
string.contains(s1,s2)判断 s1 是否包含 s2,返回 Boolean
string.length(s)求字符串长度,返回 Long
string.startsWith(s1,s2)s1 是否以 s2 开始,返回 Boolean
string.endsWith(s1,s2)s1 是否以 s2 结尾,返回 Boolean
string.substring(s,begin[,end])截取字符串 s,从 begin 到 end,如果忽略 end 的话,将从 begin 到结尾,与 java.util.String.substring 一样。
string.indexOf(s1,s2)java 中的 s1.indexOf(s2),求 s2 在 s1 中 的起始索引位置,如果不存在为-1
string.split(target,regex,[limit])Java 里的 String.split 方法一致,2.1.1 新增函数
string.join(seq,seperator)将集合 seq 里的元素以 seperator 为间隔 连接起来形成字符串,2.1.1 新增函数
string.replace_first(s,regex,replacement)Java 里的 String.replaceFirst 方法, 2.1.1 新增
string.replace_all(s,regex,replacement)Java 里的 String.replaceAll 方法 , 2.1.1 新增

4.3 数学函数

函数名称说明
math.abs(d)求 d 的绝对值
math.round(d)四舍五入
math.floor(d)向下取整
math.ceil(d)向上取整
math.sqrt(d)求 d 的平方根
math.pow(d1,d2)求 d1 的 d2 次方
math.log(d)求 d 的自然对数
math.log10(d)求 d 以 10 为底的对数
math.sin(d)正弦函数
math.cos(d)余弦函数
math.tan(d)正切函数
math.atan(d)反正切函数
math.acos(d)反余弦函数
math.asin(d)反正弦函数

4.4 Sequence 函数(集合处理)

函数名称说明
repeat(n, x)返回一个 List,将元素 x 重复 n 次组合而成。
repeatedly(n, f)返回一个 List,将函数 f 重复调用 n 次的结果组合而成。
seq.array(clazz, e1, e2,e3, ...)创建一个指定 clazz 类型的数组,并添加参数 e1,e2,e3 ...到这个数组并返回。 clazz 可以是类似 java.lang.String 的类型,也可以是原生类型,如 int/long/float 等
seq.array_of(clazz, size1, size2, ...sizes)创建 clazz 类型的一维或多维数组,维度大小为 sizes 指定。clazz 同 seq.array 定义。
seq.list(p1, p2, p3, ...)创建一个 java.util.ArrayList 实例,添加参数到这个集合并返回。
seq.set(p1, p2, p3, ...)创建一个 java.util.HashSet 实例,添加参数到这个集合并返回。
seq.map(k1, v1, k2, v2, ...)创建一个 java.util.HashMap 实例,参数要求偶数个,类似 k1,v1 这样成对作为 key-value 存入 map,返回集合。
seq.entry(key, value)创建 Map.Entry 对象,用于 map, filter 等函数
seq.keys(m)返回 map 的 key 集合
seq.vals(m)返回 map 的 value 集合
into(to_seq, from_seq)用于 sequence 转换,将 from sequence 的元素使用 seq.add 函数逐一添加到了 to sequence 并返回最终的 to_seq
seq.contains_key(map, key)当 map 中存在 key 的时候(可能为 null),返回 true。对于数组和链表,key 可以是 index,当 index 在有效范围[0..len-1],返回 true,否则返回 false
seq.add(coll, element)seq.add(m, key, value)往集合 coll 添加元素,集合可以是 java.util.Collection,也可以是 java.util.Map(三参数版本)
seq.put(coll, key, value)类似 List.set(i, v)。用于设置 seq 在 key 位置的值为 value,seq 可以是 map ,数组或者 List。 map 就是键值对, 数组或者 List 的时候, key 为索引位置整数,value 即为想要放入该索引位置的值。
seq.remove(coll, element)从集合或者 hash map 中删除元素或者 key
seq.get(coll, element)从 list、数组或者 hash-map 获取对应的元素值,对于 list 和数组, element 为元素的索引位置(从 0 开始),对于 hash map 来说, element 为 key。
map(seq,fun)将函数 fun 作用到集合 seq 每个元素上, 返回新元素组成的集合
filter(seq,predicate)将谓词 predicate 作用在集合的每个元素 上,返回谓词为 true 的元素组成的集合
count(seq)返回集合大小,seq 可以是数组,字符串,range ,List 等等
is_empty(seq)等价于 count(seq) == 0,当集合为空或者 nil,返回 true
distinct(seq)返回 seq 去重后的结果集合。
is_distinct(seq)当 seq 没有重复元素的时候,返回 true,否则返回 false
concat(seq1, seq2)将 seq1 和 seq2 “连接”,返回连接后的结果,复杂度 O(m+n), m 和 n 分别是两个集合的长度。
include(seq,element)判断 element 是否在集合 seq 中,返回 boolean 值,对于 java.uitl.Set 是 O(1) 时间复杂度,其他为 O(n)
sort(seq, [comparator])排序集合,仅对数组和 List 有效,返回排序后的新集合,comparator 是一个 java.util.Comparator 实例,可选排序方式。
reverse(seq)将集合元素逆序,返回新的集合。
reduce(seq,fun,init)fun 接收两个参数,第一个是集合元素, 第二个是累积的函数,本函数用于将 fun 作用在结果值(初始值为 init 指定)和集合的每个元素上面,返回新的结果值;函数返回最终的结果值
take_while(seq, pred)遍历集合 seq,对每个元素调用 pred(x),返回 true则加入结果集合,最终返回收集的结果集合。也就是说从集合 seq 收集 pred 调用为 true 的元素。
drop_while(seq, pred)与 take_while 相反,丢弃任何 pred(x) 为 true 的元素并返回最终的结果集合。
group_by(seq, keyfn)对集合 seq 的元素按照 keyfn(x) 的调用结果做分类,返回最终映射 map。
zipmap(keys, values)返回一个 HashMap,其中按照 keys 和 values 两个集合的顺序映射键值对。
seq.every(seq, fun)fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的每个元素调用 fun 后都返回 true 的时候,整个调用结果为 true,否则为 false。
seq.not_any(seq, fun)fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的每个元素调用 fun 后都返回 false 的时候,整个调用结果为 true,否则为 false。
seq.some(seq, fun)fun 接收集合的每个元素作为唯一参数,返回 true 或 false。当集合里的只要有一个元素调用 fun 后返回 true 的时候,整个调用结果立即为该元素,否则为 nil。
seq.eq(value)返回一个谓词,用来判断传入的参数是否跟 value 相等,用于 filter 函数,如filter(seq,seq.eq(3)) 过滤返回等于3 的元素组成的集合
seq.neq(value)与 seq.eq 类似,返回判断不等于的谓词
seq.gt(value)返回判断大于 value 的谓词
seq.ge(value)返回判断大于等于 value 的谓词
seq.lt(value)返回判断小于 value 的谓词
seq.le(value)返回判断小于等于 value 的谓词
seq.nil()返回判断是否为 nil 的谓词
seq.exists()返回判断不为 nil 的谓词
seq.and(p1, p2, p3, ...)组合多个谓词函数,返回一个新的谓词函数,当今仅当 p1、p2、p3 ...等所有函数都返回 true 的时候,新函数返回 true
seq.or(p1, p2, p3, ...)组合多个谓词函数,返回一个新的谓词函数,当 p1, p2, p3... 其中一个返回 true 的时候,新函数立即返回 true,否则返回 false。
seq.min(coll)返回集合中的最小元素,要求集合元素可比较(实现 Comprable 接口),比较规则遵循 aviator 规则。
seq.max(coll)返回集合中的最大元素,要求集合元素可比较(实现 Comprable 接口),比较规则遵循 aviator 规则。

参考

AviatorScript 编程指南(5.0) · 语雀

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值