定义和赋值
和其他语言类似, AviatorScript 也允许定义变量,变量有特定的类型和“指向”的值。不过 AviatorScript 中变量的定义和赋值是不可分割的,定义一个变量的同时需要给他赋值:
## 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
type
函数会将参数的类型以字符串的形式返回,包括:
- long 整数
- double 浮点数
- decimal 高精度数字类型
- bigint 大整数
- boolean 布尔类型
- string 字符串类型
- pattern 正则表达式
- range 用于循环语句的
Range
类型 - function 函数,参见第七章。
- nil 特殊变量 nil,下文解释。
动态类型
AviatorScript 是动态类型语言,变量的类型随着赋值而改变:
## 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);
}
s = 99;
println(type(s));
println(a + s);
s 原来是一个字符串,我们通过赋值 s = 99
,他的类型变为了数字,就可以参与算术运算。
nil
当我们想表示一个变量还没有赋值的时候,需要用到 nil
这个特殊类型,它和 java 中的 null
作用一样,表示当前变量还没有赋值。nil 最常见的场景就是用于判断变量是否为 null:
## examples/nil.av
a = nil;
b = 99;
if a == nil {
println("a is " + type(a));
}
a = 3;
if a !=nil {
println(a + b);
}
输出:
a is nil
102
在 Java 中, null 只能参与等于和不等于的比较运算,而在 AviatorScript 中, nil 可以参与所有的比较运算符,只是规定任何类型都比**nil**
大除了**nil**
本身:
## examples/nil.av
a = 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: true
nil >= a: false
"" >= nil: true
nil > "": false
0.0 > nil: true
nil >= 0.0: false
/\s/ > nil: true
nil <= /\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 name
String s = (String) compiledExp.execute();
System.out.println(s);
// inject name
s = (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.av
p("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.141592653589793
AviatorEvaluator.VERSION is: 5.1.5-SNAPSHOT
AviatorEvaluator.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.VERSION
at 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: com
at 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
区别就是会包括了含有 .
的变量名。