定义和赋值
和其他语言类似, AviatorScript 也允许定义变量,变量有特定的类型和“指向”的值。不过 AviatorScript 中变量的定义和赋值是不可分割的,定义一个变量的同时需要给他赋值:
## examples/type.ava = 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 的类型,它会返回代表具体类型的字符串,上述代码执行将打印:
longstringpatternAviatorScript
type 函数会将参数的类型以字符串的形式返回,包括:
- long 整数
- double 浮点数
- decimal 高精度数字类型
- bigint 大整数
- boolean 布尔类型
- string 字符串类型
- pattern 正则表达式
- range 用于循环语句的
Range类型 - function 函数,参见第七章。
- nil 特殊变量 nil,下文解释。
动态类型
AviatorScript 是动态类型语言,变量的类型随着赋值而改变:
## examples/type.ava = 1;println(type(a));s = "Hello AviatorScript";println(type(s));let p = /(?i)hello\s+(.*)/;println(type(p));if s =~ p {println($1);}s = 99;println(type(s));println(a + s);
s 原来是一个字符串,我们通过赋值 s = 99 ,他的类型变为了数字,就可以参与算术运算。
nil
当我们想表示一个变量还没有赋值的时候,需要用到 nil 这个特殊类型,它和 java 中的 null 作用一样,表示当前变量还没有赋值。nil 最常见的场景就是用于判断变量是否为 null:
## examples/nil.ava = nil;b = 99;if a == nil {println("a is " + type(a));}a = 3;if a !=nil {println(a + b);}
输出:
a is nil102
在 Java 中, null 只能参与等于和不等于的比较运算,而在 AviatorScript 中, nil 可以参与所有的比较运算符,只是规定任何类型都比**nil**大除了**nil**本身:
## examples/nil.ava = 3;println("a > nil: " + (a > nil));println("nil >= a: " + (nil >= a));println("\"\" >= nil: "+ ("" >= nil));println("nil > \"\": " + (nil > ""));println("0.0 > nil: " + (0.0 > nil));println("nil >= 0.0: " + (nil >= 0.0));println("/\\s/ > nil: " + (/\s/ > nil));println("nil <= /\\s/: " + (nil <= /\s/));
输出:
a > nil: truenil >= a: false"" >= nil: truenil > "": false0.0 > nil: truenil >= 0.0: false/\s/ > nil: truenil <= /\s/: true
除了比较运算符之外, nil 不能参与其他运算,比如你不能将 nil 和一个整数相加,这将报错:
c = nil;c + 3;
报错信息: Could not add
Exception in thread "main" com.googlecode.aviator.exception.ExpressionRuntimeException:Could not add <JavaType, c, null, null> with <Long, 3>at com.googlecode.aviator.runtime.type.AviatorObject.add(AviatorObject.java:97)at com.googlecode.aviator.runtime.type.AviatorJavaType.add(AviatorJavaType.java:638)
传入变量
AviatorScript 一开始是一个表达式引擎,因此是允许执行的时候传入变量,在 2.1 编译和执行我们已经看到一个例子:
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** :
String expression = "name != nil ? ('hello, ' + name):'who are u?'";Expression compiledExp = AviatorEvaluator.compile(expression);// we don't inject variable nameString s = (String) compiledExp.execute();System.out.println(s);// inject names = (String) compiledExp.execute(compiledExp.newEnv("name", "dennis"));System.out.println(s);
表达式里用三元运算符判断变量 name 是否为 null,并返回不同的值,例子中我们先直接执行,不传入 name,然后再将 name 绑定为 dennis 重新执行:
who are u?hello, dennis
变量的语法糖
Aviator 有个方便用户使用变量的语法糖, 当你要访问变量a中的某个属性b, 那么你可以通过a.b访问到, 更进一步, a.b.c将访问变量a的b属性中的c属性值, 推广开来也就是说 Aviator 可以将变量声明为嵌套访问的形式。TestAviator类符合JavaBean规范, 并且是 public 的,我们就可以使用语法糖:
package com.googlecode.aviator.example;import java.util.Date;import java.util.HashMap;import java.util.Map;import com.googlecode.aviator.AviatorEvaluator;public class TestAviator {int i;float f;Date date;public TestAviator(final int i, final float f, final Date date) {this.i = i;this.f = f;this.date = date;}public int getI() {return this.i;}public void setI(final int i) {this.i = i;}public float getF() {return this.f;}public void setF(final float f) {this.f = f;}public Date getDate() {return this.date;}public void setDate(final Date date) {this.date = date;}public static void main(final String[] args) {TestAviator foo = new TestAviator(100, 3.14f, new Date());Map<String, Object> env = new HashMap<String, Object>();env.put("foo", foo);System.out.println(AviatorEvaluator.execute("'foo.i = '+foo.i", env));System.out.println(AviatorEvaluator.execute("'foo.f = '+foo.f", env));System.out.println(AviatorEvaluator.execute("'foo.date.year = '+ (foo.date.year+1990)", env));}}
这个实现的基础是 commons-beanutils,基于反射实现。但是 AviatorScript 也做了优化,如果访问路径上的每一级变量都是 Map,会直接调用 Map#get(key) 访问,避免反射调用。
引用变量
对于深度嵌套并且同时有数组的变量访问,例如 foo.bars[1].name,从 3.1.0 版本开始, aviator 通过引用变量来支持(quote variable):
AviatorEvaluator.execute("'hello,' + #foo.bars[1].name", env)
引用变量要求以 # 符号开始,变量如果包含特殊字符串,需要使用两个 符号来包围,并且变量名中不能包含其他变量,也就是并不支持#foo.bars[i].name` 这样的访问,如果有此类特殊需求,请通过自定义函数实现。
aviatorscript 内部支持了 3 种特殊语法(基于 PropertyUtilsBean 实现):
- 索引
name[index],name 是数组或者链表可以通过索引位置 index 访问,但是 index 不能是变量或者表达式 - 映射
name(key),如果 name 是 map 类型,并且 key 的类型为 String,可以通过此表达式获取 value。 - 融合的,比如
name1[index].name2(key)。
请注意,因为 name(key) 可能被解析为方法调用,因此这里需要上面提到的特殊的语法访问 #name(key)
对于一些深度嵌套的 List 或者数组的访问, commons-beanutils 还支持类似 #map.array.[0].name这样的访问语法,如果不满足JavaBean规范的,请尝试使用这样的语法做嵌套访问。
此外,对于嵌套变量也支持赋值,只要它有相应的 setter 方法即可:
System.out.println(AviatorEvaluator.execute("foo.i = 100; foo.f + foo.i", env));
这里的例子我们定义的都是脚本范围内的全局变量,局部变量和作用域参见下一节《3.5 作用域》
访问 Java 静态变量
从 5.2 开始,你可以直接访问 Java 类的静态变量,比如 Math.PI :
## examples/static_vars.avp("Math.PI is: " + Math.PI);use com.googlecode.aviator.AviatorEvaluator;p("AviatorEvaluator.VERSION is: " + AviatorEvaluator.VERSION);p("AviatorEvaluator.COMPILE is: " + AviatorEvaluator.COMPILE);
尝试执行将输出:
Math.PI is: 3.141592653589793AviatorEvaluator.VERSION is: 5.1.5-SNAPSHOTAviatorEvaluator.COMPILE is: 0
对于 java.lang 下面的类可以直接访问,比如 Math 等,但是对于其他包下面的类,都要求先用 use 语法导入该类到当前上下文,然后通过 Class.Var 的方式访问,比如上面例子中的 AviatorEvaluator.VERSION ,不能通过完整类名的方式来访问,这将报错:
p(com.googlecode.aviator.AviatorEvaluator.VERSION);
Exception in thread "main" com.googlecode.aviator.exception.ExpressionRuntimeException: Could not find variable com.googlecode.aviator.AviatorEvaluator.VERSIONat com.googlecode.aviator.runtime.type.AviatorJavaType.getProperty(AviatorJavaType.java:442)at com.googlecode.aviator.runtime.type.AviatorJavaType.getValueFromEnv(AviatorJavaType.java:329)at com.googlecode.aviator.runtime.type.AviatorJavaType.getValue(AviatorJavaType.java:317)at com.googlecode.aviator.runtime.function.system.PrintlnFunction.call(PrintlnFunction.java:54)at Script_1605786165591_47/1644443712.execute0(static_vars.av:11)at com.googlecode.aviator.ClassExpression.executeDirectly(ClassExpression.java:65)at com.googlecode.aviator.BaseExpression.execute(BaseExpression.java:136)at com.googlecode.aviator.Main.main(Main.java:45)Caused by: java.lang.NullPointerException: comat com.googlecode.aviator.runtime.type.AviatorJavaType.fastGetProperty(AviatorJavaType.java:542)at com.googlecode.aviator.runtime.type.AviatorJavaType.getProperty(AviatorJavaType.java:433)... 7 more
特殊变量
AviatorScript 内置了部分特殊变量(启用了 Feature.InternalVars 才可以访问):
__exp__(注意是双下划线),当前表达式的Expression对象。__env__当前上下文环境 env__instance__当前执行引擎AviatorEvaluatorInstance实例__args__当前函数调用的参数列表。
我们可以简单打印看下 __env__ 看下:
AviatorEvaluator.execute("let a = 1; p(__env__)");
输出:
com.googlecode.aviator.utils.Env@7cca494b{__instance__=com.googlecode.aviator.AviatorEvaluatorInstance@7ba4f24f,__exp__=Script_1596184736312_0/1149319664@3b9a45b3,__env__=<this>,a=1}
可以看到所有的变量包括内部特殊变量,都可以在 __env__ 里找到。
外部变量(未初始化全局变量)
从编译后的 Expression 对象,可以获取脚本中未初始化的全局变量列表,通过 getVariableNames 方法:
Expression expression = AviatorEvaluator.compile("b + a", true);List<String> vars = expression.getVariableNames();
vars 列表将是 b 和 a ,更复杂一点的例子:
Expression exp = AviatorEvaluator.compile("b=2; if(a > 1) { a + b } elsif( a > 10) { return a + c; } else { return 10; }");List<String> vars = exp.getVariableNames();
上面这个 vars 列表将是 [a, c] 。该方法可以识别出所有脚本中(哪怕是嵌套循环或者闭包)里的未初始化全局变量。
如果你的全局变量名称可能是 a.b.c 这样的命名,那么可以使用 getVariableFullNames() 来获取完整命名,它和 getVariableNames 区别就是会包括了含有 . 的变量名。
