Google Aviator语法手册详解

转载自:Google Aviator语法手册 – 知乎 (zhihu.com)

背景

Google Aviator是一种基于字节码的高性能Java表达式求值引擎,专注于提供快速的表达式计算能力。它主要用于动态计算表达式,特别是在需要频繁计算的场景中。Aviator支持的表达式范围包括但不限于以下几个方面:

  1. 算术和数值运算: Aviator 支持基本的算术运算符(+、-、*、/、%)以及数值运算,包括整数和浮点数。
  2. 比较和逻辑运算: Aviator 支持比较运算符(==、!=、>、<、>=、<=)和逻辑运算符(&&、||、!),用于比较和操作布尔值。
  3. 字符串操作: Aviator 允许对字符串进行拼接(+)和比较运算,还支持字符串长度计算。
  4. 三元表达式: 支持类似于 Java三元表达式(? :),用于条件判断和值选择。
    1. 注意,跟 java 不同的是 AviatorScript 允许两个分支返回的结果类型可以是不兼容的。比如:a > b ? "a > b" : 999 两个分支的结果分别是字符串和数字,这在 AviatorScript 中是完全可以的。
  1. 成员访问: Aviator 允许访问对象的成员变量和方法,可以通过`.`符号进行访问。
  2. 函数调用: Aviator 内置了一些常用的函数,如数学函数、字符串函数等,同时也支持自定义函数的调用。
  3. 集合操作: Aviator 支持对数组和列表的遍历和访问,可以执行类似于 foreach 的操作。
  4. 正则表达式: Aviator 提供了对正则表达式的支持,可以用于字符串匹配和替换。
  5. 条件判断: Aviator 支持 if-else 条件判断,根据条件来执行不同的操作。
  6. 循环控制: Aviator 支持 for 循环,可以用于多次迭代操作。
  7. 空值处理: Aviator 支持对空值的判断和处理,可以进行空值的条件判断和操作。
  8. 位运算: Aviator 支持位运算符(&、|、^、~、<<、>>、>>>),用于对整数进行位操作。

总体而言,Aviator提供了丰富的表达式求值能力,涵盖了算术、逻辑、字符串、集合等多个方面,使得开发人员能够在各种场景下高效地计算和处理表达式。下面我们将根据各个不同的方面展开细节讲解,希望能够帮你提高开发效率。

Aviator 自带方法与使用

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

基础数据类型

Google Aviator 表达式引擎是基于 JVM 去工作,所以语法和 JAVA 是高度相似的,但是又基于 JAVA 进行了精简,现在我们先了解一下Aviator 表达式引擎所支持的数据类型:数字型(整数和浮点数)、字符串型。

通过 type(x) 可以判断数据类型是什么。

数字型-整数-Long 类型:

  • AviatorScript 中并没有 byte/short/int 等类型,统一整数类型都为 long,支持的范围也跟 java 语言一样:-9223372036854774808~9223372036854774807。
  • 支持的加减乘除四则运算,需要注意,整数相除的结果仍然是整数,比如例子中的 a/b 结果就是 0,遵循 java 的整数运算规则。通常来说,复杂的算术表达式,从代码可读性和稳健角度,都推荐使用括号来强制指定优先级。
  • 数字型-整数-大整数(BigInt):

  • 任何超过 long 范围的整数字面量,会自动提升为 BigInteger 对象(以下简称 BigInt),任何数字以 N 字母结尾就自动变 BigInt
  • 请注意,默认的 long 类型在计算后如果超过范围溢出后,不会自动提升为 BigInt,但是 BigInt 和 long 一起参与算术运算的时候,结果为 BigInt 类型。关于类型转换的规则,我们后面再详细介绍。
  • 数字型-浮点数-double

  • 数字除了整数之外,AviatorScript 同样支持浮点数,但是仅支持 double 类型,也就是双精度 64 位,符合 IEEE754 规范的浮点数。传入的 java float 也将转换为 double 类型。所有的浮点数都被认为是 double 类型。浮点数的表示形式有两种:
  • 十进制的带小数点的数字,比如 1.34159265 , 0.33333 等等。
  • 科学计数法表示,如 1e-2 , 2E3 等等,大小写字母 e 皆可。
  • 浮点数的运算符跟整数一样,同样支持加减乘除,优先级也是一样。浮点数和浮点数的算术运算结果为浮点数,浮点数和整数的运算结果仍然为浮点数。
  • 数字型-高精度浮点数-Decimal

  • 计算货币之类还是建议用整数类型存储分单位的数值,直接避免到出现误差的可能性
  • 浮点数是无法用于需要精确运算的场景,比如货币运算或者物理公式运算等,这种情况下如果在 Java 里一般推荐使用 BigDecimal 类型,调用它的 add/sub 等方法来做算术运算。
  • AviatorScript 将 BigDecimal 作为基本类型来支持(下文简称 decimal 类型),只要浮点数以 M 结尾就会识别类型为 deicmal,例如 1.34M 、 0.333M 或者科学计数法 2e-3M 等等。
  • decimal 同样使用 +,-,*,/ 来做算术运算, AviatorScript 重载了这些运算符的方法,自动转成 BigDecimal 类的各种运算方法。
  • 除了 double 以外的数字类型和 decimal 一起运算,结果为 decimal。任何有 double 参与的运算,结果都为 double。
    数字型-数字类型转换
  • 数字类型在运算的时候,会遵循一定的类型转换规则:
    1. 单一类型参与的运算,结果仍然为该类型,比如整数和整数相除仍然是整数,double 和 double 运算结果还是 double。
    2. 多种类型参与的运算,按照下列顺序: long -> bigint -> decimal -> double 自动提升,比如 long 和 bigint 运算结果为 bigint, long 和 decimal 运算结果为 decimal,任何类型和 double 一起运算结果为 double
  • 你可以通过 long(x) 将一个数字强制转化为 long,这个过程中可能丢失精度,也可以用 double(x) 将一个数字强转为 double 类型。
    字符串型-数字类型转换
  • 在任何语言中,字符串都是最基本的类型,比如 java 里就是 String 类型。AviatorScript 中同样支持字符串,只要以单引号或者双引号括起来的连续字符就是一个完整的字符串对象,例如:
    1. "hello world"
    2. 'hello world'
    3. "a" 或者 'a'
  • 字符串的长度可以通过 string.length 函数获取
  • 字符串拼接可以用 `+` 号(这又是一个运算符重载)
  • 正则表达式

    Aviator 的正则表达式使用与 JAVA 完全相同,示例:/^(.*).av$/

    运算符

    幂运算

    从 5.1.3 开始,AviatorScript 引入幂运算符 ** ,原来使用 math.pow(a, b) 的代码都可以写成 a**b ,幂运算符的优先级较高,在单目运算符之上。幂运算的基本规则是:

    1. 基数和指数都为 long ,并且指数为正数,结果为 long
    2. 基数和指数都为 long,并且指数为负数,结果为 double
    3. 基数为 decimal,指数取整数部分(int value),等价于 BigDecimal#pow(int) 。
    4. 基数为 bigint,指数取整数部分(int value),等价于 BigInteger#pow(int) 。
    5. 基数或者指数任一为 double,结果为 double

    位运算

    我们还没有介绍的是数字的位运算,位运算仅支持整数 long,跟 Java 保持一致:

  • & 位与运算
  • | 或运算
  • ^ 异或运算
  • ~ 一元非运算
  • << 左移运算
  • >> 右移运算
  • >>> 无符号右移
  • 运算符优先级

    完整的运算符优先级的优先顺序如下表,基本跟 java 保持一致,除了特别引入的正则匹配(记住这个优先级没有太大必要,推荐任何情况都使用括号来明确优先级):

    优先级 运算符 结合性 备注
    1 ( ) [ ]  . 从左到右 括号,数组,小数点
    2 ** 从左到右
    3 !  ~ 从右到左
    4 *  /  % 从左到右
    5 +  – 从左到右
    6 <<  >>  >>> 从左到右
    7 <  <=  >  >= 从左到右
    8 ==  != 从左到右
    9 & | ! 从左到右 与或非
    10 && 从左到右
    11 ? : 从左到右
    12 = =~ 从右到左

    三元运算符

    String expression = "name != nil ? ('hello, ' + name):'who are u?'";

    自定义运算符

    前面我们已经看了运算符重载的例子,比如加减乘除,可以用于 long/double/bigint/decimal 等多种数字类型,这就是一个重载,比如加号 + 可以用于数字,也可以用于拼接字符串,这又是一个重载。

    你也可以自定义任意运算符的行为,比如我们想将整数相除的结果修改为浮点数,可以:

    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"));

    }

    }

    通过 AviatorEvaluatorInstance#addOpFunction(opType, function) 就可以自定义运算符的行为,这个例子中如果我们发现 arg1/arg2 的类型都是 long ,那么我们就将 arg2 转成 double 并计算结果,结果需要包装成 AviatorDouble 类型返回;如果不是,我们继续调用原来的除法。所有的运算符都可以在 OperatorType 找到,你都可以自定义他的行为。

    从这个例子中我们也可以看出 AviatorScript 中的所有类型都是 AviatorObject 的子类,他们包括:

  • AviatorLong 表示整数 long 类型
  • AviatorDouble 表示浮点数 double 类型
  • AviatorBigInt 表示 bigint 大整数
  • AviatorDecimal 表示 decimal 类型
  • AviatorString 表示字符串
  • AviatorPattern 表示正则表达式
  • AviatorNil 特殊类型 nil
  • AviatorJavaType 表示变量
  • 可以参见 AviatorType 这个枚举类。所有运算符都实现为 AviatorObject 的一个方法,比如加法就是 add(arg2, env) ,减法就是 sub 等等,具体见 AviatorObject 文档。比较运算符的实现是基于 compare(arg2, env) 方法返回的结果,如果为 0 ,表示相等,大于返回正数,小于返回负数。

    变量

    基础变量使用

    Google Aviator 支持定义变量,如果使用到的变量没有默认赋值,会为 nil 值(等于 JAVA 中的 null)。变量赋值和使用示例如下:

    1. 赋值为空(在 Java 中, null 只能参与等于和不等于的比较运算,而在 Aviator Script 中, nil 可以参与所有的比较运算符,只是规定任何类型都比nil大除了nil本身):a = nil;
    2. 赋值为数字:a = 98;
    3. 判空:a == nil
    4. 判不为空:a != nil

    变量语法糖

    Aviator 有个方便用户使用变量的语法糖, 当你要访问变量a中的某个属性b, 那么你可以通过a.b访问到, 更进一步, a.b.c将访问变量a的b属性中的c属性值, 推广开来也就是说 Aviator 可以将变量声明为嵌套访问的形式。

    这个实现的基础是 commons-beanutils,基于反射实现。但是 AviatorScript 也做了优化,如果访问路径上的每一级变量都是 Map,会直接调用 Map#get(key) 访问,避免反射调用。

    引用变量

    对于深度嵌套并且同时有数组的变量访问,例如 foo.bars[1].name,从 3.1.0 版本开始, aviator 通过引用变量来支持(quote variable)。

    从 5.2 版本开始可以直接访问 Java 类的静态变量,比如 `Math.PI`

    多行表达式和 return

    看这样一个例子:

    let a = 1;

    let b = 2;

    c = a + b;

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

    let a = 1;

    let b = 2;

    c = a + b

    这时候再执行 execute 将返回表达式 c = a +b 的值,赋值语句的结果即为右值,也就是 3。在 Aviator Script 中任何表达式都有一个值,加上分号后就是丢弃该值固定为 nil。除了不加分号来返回之外,你也可以用 return 语句来指定返回:

    let a = 1;

    let b = 2;

    c = a + b;

    return c;

    注意, return 语句就必须加上分号才是完整的一条语句,否则将报语法错误

    return 也用于提前返回,结合条件语句可以做更复杂的逻辑判断:

    if a < b {

    return "a is less than b.";

    }

    return a – b;

    use 语句引用 Java 类

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

    use java.util.Date;

    let d = new Date();

    p(type(d));

    p(d);

    use 包名.类名 就可以导入任意一个类到当前上下文。

    如果要导入某个包下面的任意类,可以用通配符 *

    甚至可以把防并发的包引入,做防并发处理。

    条件语句

    AviatorScript 中的条件语句和其他语言没有太大区别。if 接受一个布尔表达式,如果其值为 true 就执行后续的代码块。如果为 false ,可以带上 else 语句执行其中的代码块,代码块都是以大括号包起来(请注意,代码块都必须用大括号包起来,哪怕是单行语句,这跟 java 是不一样的):

    if(false) {

    println("in if body");

    } else {

    println("in else body");

    }

    if 后面连着的表达式的括号是可以忽略,上面的例子可以改写成:

    if false { println("in if body"); } else { println("in else body"); }

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

    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 + ".");

    同样,同样 elsif 对应的判断语句的括号也是可以忽略的。

    循环语句

    循环语句通常用于遍历一个集合,或者重复执行若干指令,直到满足某个条件等等。 AviatorScript 支持 for 和 while 两种循环语句。

    for 循环

    示例如下

    for i in range(0, 10) {

    println(i);

    }

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

    range 函数还可以接受第三个参数,表示递增的 step 大小(不传默认 step 就是 1,步长的概念),比如我们可以打印 0 到 9 之间的偶数:

    for i in range(0, 10, 2) {

    println(i);

    }

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

    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.key 和 x.value 来访问每一对的键值。list 就是一个 1 到 9 整数组成的 List ,我们利用 for 语句迭代累计它们的和 sum 并打印。

    如果你想在执行代码块中途跳过剩余代码,继续下个迭代,可以用 continue。同样,如果想中途跳出迭代,你可以用 break。return 有类似 break 的效果,也可以从循环中跳出,但是它会将整个脚本(或者函数)中断执行并返回,而不仅仅是跳出循环。

    while 循环

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

    let sum = 1;

    while sum < 1000 {

    sum = sum + sum;

    }

    println(sum);

    while 后面跟 if 一样跟着一个布尔表达式,括号同样可以省略,但是代码块必须用大括号包围起来,这跟 if/for 也是一样的。同样, while 也可以用 break 和 continue 语句。使用 while true 来无限循环。return/continue 的使用和 for 类似,不再赘述。循环语句也可以多层嵌套,这跟其他语言都没有什么两样。

    自定义函数和调用 Java 方法

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

    1. 自定义函数
    2. 自动导入 java 类方法
    3. FunctionMissing 机制

    这里主要介绍自定义函数

    自定义函数

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

    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";

    }

    }

    所有的函数都实现了 AviatorFunction 接口。它有一系列 call 方法,根据参数个数不同而重载,其中 getName() 返回方法名。一般来说都推荐继承 com.googlecode.aviator.runtime.function.AbstractFunction ,并覆写对应参数个数的方法即可,例如上面例子中定义了 add 方法,它接受两个参数。第一个参数是当前执行的上下文,arg1 和 arg2 分别表示从左到右的两个参数,最终结果是另一个 AviatorObject ,这个实现中是将两个参数相加,返回浮点结果 AviatorDouble 。

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

    自定义可变参数函数

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

    import java.util.Map;

    import com.googlecode.aviator.runtime.function.AbstractVariadicFunction;

    import com.googlecode.aviator.runtime.type.AviatorObject;

    import com.googlecode.aviator.runtime.type.AviatorRuntimeJavaType;

    /**

    * tuple(x,y,z, …) function to return an object array.

    *

    * @author dennis

    * @since 4.0.0

    *

    */

    public class TupleFunction extends AbstractVariadicFunction {

    private static final long serialVersionUID = -7377110880312266008L;

    @Override

    public String getName() {

    return "tuple";

    }

    @Override

    public AviatorObject variadicCall(Map<String, Object> env, AviatorObject… args) {

    Object[] tuple = new Object[args.length];

    for (int i = 0; i < args.length; i++) {

    tuple[i] = args[i].getValue(env);

    }

    return AviatorRuntimeJavaType.valueOf(tuple);

    }

    }

    核心就是实现 variadicCall(Map<String, Object> env, AviatorObject… args) 方法,其中的 args 就是可变的参数列表。

    数组和集合

    count 可以获取数组长度:

    println("count of t: "+ count(t));

    遍历数组

    let a = seq.array(int, 1, 2, 3.3, 4);

    for x in a {

    println(x);

    }

    判断元素是否存在

    对于数组、List 和 Set 来说,判断某个元素是否存在都应该用 include(coll, element) 函数,对于数组和 List 来说,这个函数的时间复杂度是 O(n),因为要遍历整个数组或链表;对于 Set 来说是 O(1) 时间复杂度,直接调用用了 Set#contains 方法。

    for i in range(0, 3) {

    assert(include(list, i));

    assert(include(set, i));

    }

    assert(!include(list, 5));

    assert(!include(set, 5));

    对于 map 来说,如果是判断 key 是否存在,需要用 seq.contains_key(coll, key) :

    for i in range(0, 3) {

    assert(seq.contains_key(map, i));

    }

    assert(!seq.contains_key(map, 5));

    遍历集合

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

    ## Iterate the collection by for..in loop

    println("list elements:");

    for x in list {

    println(x);

    }

    println("set elements:");

    for x in set {

    println(x);

    }

    println("map elements:");

    for x in map {

    println(x.key + "=" + x.value);

    }

    对于 map 来说迭代循环中的元素就是 Map.Entry 对象,可以通过 key 和 value 属性来访问键和值。

    删除元素 seq.remove

    删除元素也是常见的需求,可以用 seq.remove(coll, element) ,对于 List/Set 和 Map 都是如此,如果是 Map,传入的应该是 key:

    ## remove elements

    assert(list == seq.remove(list, 2));

    assert(list == seq.remove(list, 4));

    assert(set == seq.remove(set, 1));

    assert(map == seq.remove(map, 0));

    println("list: " + list);

    println("set: " + set);

    println("map: " + map);

    删除不存在的元素不产生影响。 seq.remove 返回的是删除后的集合对象。

    操作 sequence 的高阶函数

    对于 Sequence 的抽象, AviatorScript 也提供了一套高阶函数来方便地对集合做转换、过滤、查询以及聚合,我们将一一介绍。这些函数的规则都是将 sequence 作为第一个参数。

    Count

    count(seq) 函数用于获取 seq 里的集合元素,它将尽量在 O(1) 的时间复杂度内返回结果,最差情况下退化成 O(n):

    ## count

    println("count of array: " + count(a));

    println("count of range: " + count(r));

    println("count of set: " + count(s));

    println("count of map: " + count(m));

    println("count of list: " + count(n));

    is_empty

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

    println("is_empty(array): " + is_empty(a));

    println("is_empty(seq.list()): " + is_empty(seq.list()));

    println("is_empty(nil): " + is_empty(nil));

    include

    include(seq, x) 用于判断元素 x 是否在 seq 内,对于 Set 是 O(1) 的时间复杂度,其他是 O(n):

    ## include

    println("array has 3: " + include(a, 3));

    println("map has an entry ('b', 2): " + include(m, seq.entry("b", 2)));

    println("range has 10: " + include(r, 10));


    参考资料

    1. https://www.yuque.com/boyan-avfmj/aviatorscript/cpow90
    2. https://juejin.cn/post/6992213605861523493
    3. https://juejin.cn/post/6992586914373042207?searchId=202308142048456CF580127A23B5275785
    4. https://juejin.cn/post/6992956846591983647
    5. https://juejin.cn/post/6993319031470030884
    6. https://juejin.cn/post/6993688391816577061
    7. https://juejin.cn/post/6994068942272610340
    8. https://juejin.cn/post/6994432429855342623
    9. https://juejin.cn/post/6994803531064557575
    10. https://juejin.cn/post/6995181281029914654
    11. https://juejin.cn/post/6995558411609833486
    12. https://juejin.cn/post/6996168805709774878
    13. https://juejin.cn/post/6996170437315002381
    14. https://juejin.cn/post/6996654434096775205
    15. https://juejin.cn/post/6997008934389153806
    16. https://juejin.cn/post/6997369285593006116
    17. https://juejin.cn/post/6997778225124507678
    18. https://juejin.cn/post/6998137825489387556
    19. https://juejin.cn/post/6998512130399928357
    20. https://juejin.cn/post/6998884618598350856
    21. https://juejin.cn/post/6999255830985965575
    22. https://juejin.cn/post/6999619488635158536
    23. https://juejin.cn/post/7000013860564369444
    24. https://juejin.cn/post/7000370788998053896
    25. https://juejin.cn/post/7000742854700040222
    26. https://juejin.cn/post/7001111119246917663
    27. https://juejin.cn/post/7001489301611479077
    28. https://juejin.cn/post/7002235897873694757
    29. https://juejin.cn/post/7002606430519853069

    作者:a1124544556

    物联沃分享整理
    物联沃-IOTWORD物联网 » Google Aviator语法手册详解

    发表回复