定义和赋值

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

  1. ## examples/type.av
  2. a = 1;
  3. println(type(a));
  4. s = "Hello AviatorScript";
  5. println(type(s));
  6. let p = /(?i)hello\s+(.*)/;
  7. println(type(p));
  8. if s =~ p {
  9. println($1);
  10. }

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

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

  1. long
  2. string
  3. pattern
  4. AviatorScript

type 函数会将参数的类型以字符串的形式返回,包括:

  • long 整数
  • double 浮点数
  • decimal 高精度数字类型
  • bigint 大整数
  • boolean 布尔类型
  • string 字符串类型
  • pattern 正则表达式
  • range 用于循环语句的 Range 类型
  • function 函数,参见第七章。
  • nil 特殊变量 nil,下文解释。

动态类型

AviatorScript 是动态类型语言,变量的类型随着赋值而改变

  1. ## examples/type.av
  2. a = 1;
  3. println(type(a));
  4. s = "Hello AviatorScript";
  5. println(type(s));
  6. let p = /(?i)hello\s+(.*)/;
  7. println(type(p));
  8. if s =~ p {
  9. println($1);
  10. }
  11. s = 99;
  12. println(type(s));
  13. println(a + s);

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

nil

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

  1. ## examples/nil.av
  2. a = nil;
  3. b = 99;
  4. if a == nil {
  5. println("a is " + type(a));
  6. }
  7. a = 3;
  8. if a !=nil {
  9. println(a + b);
  10. }

输出:

  1. a is nil
  2. 102

在 Java 中, null 只能参与等于和不等于的比较运算,而在 AviatorScript 中, nil 可以参与所有的比较运算符,只是规定任何类型都比**nil**大除了**nil**本身:

  1. ## examples/nil.av
  2. a = 3;
  3. println("a > nil: " + (a > nil));
  4. println("nil >= a: " + (nil >= a));
  5. println("\"\" >= nil: "+ ("" >= nil));
  6. println("nil > \"\": " + (nil > ""));
  7. println("0.0 > nil: " + (0.0 > nil));
  8. println("nil >= 0.0: " + (nil >= 0.0));
  9. println("/\\s/ > nil: " + (/\s/ > nil));
  10. println("nil <= /\\s/: " + (nil <= /\s/));

输出:

  1. a > nil: true
  2. nil >= a: false
  3. "" >= nil: true
  4. nil > "": false
  5. 0.0 > nil: true
  6. nil >= 0.0: false
  7. /\s/ > nil: true
  8. nil <= /\s/: true

除了比较运算符之外, nil 不能参与其他运算,比如你不能将 nil 和一个整数相加,这将报错:

  1. c = nil;
  2. c + 3;

报错信息: Could not add with

  1. Exception in thread "main" com.googlecode.aviator.exception.ExpressionRuntimeException:
  2. Could not add <JavaType, c, null, null> with <Long, 3>
  3. at com.googlecode.aviator.runtime.type.AviatorObject.add(AviatorObject.java:97)
  4. at com.googlecode.aviator.runtime.type.AviatorJavaType.add(AviatorJavaType.java:638)

传入变量

AviatorScript 一开始是一个表达式引擎,因此是允许执行的时候传入变量,在 2.1 编译和执行我们已经看到一个例子:

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

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

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

  1. String expression = "name != nil ? ('hello, ' + name):'who are u?'";
  2. Expression compiledExp = AviatorEvaluator.compile(expression);
  3. // we don't inject variable name
  4. String s = (String) compiledExp.execute();
  5. System.out.println(s);
  6. // inject name
  7. s = (String) compiledExp.execute(compiledExp.newEnv("name", "dennis"));
  8. System.out.println(s);

表达式里用三元运算符判断变量 name 是否为 null,并返回不同的值,例子中我们先直接执行,不传入 name,然后再将 name 绑定为 dennis 重新执行:

  1. who are u?
  2. hello, dennis

变量的语法糖

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

  1. package com.googlecode.aviator.example;
  2. import java.util.Date;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import com.googlecode.aviator.AviatorEvaluator;
  6. public class TestAviator {
  7. int i;
  8. float f;
  9. Date date;
  10. public TestAviator(final int i, final float f, final Date date) {
  11. this.i = i;
  12. this.f = f;
  13. this.date = date;
  14. }
  15. public int getI() {
  16. return this.i;
  17. }
  18. public void setI(final int i) {
  19. this.i = i;
  20. }
  21. public float getF() {
  22. return this.f;
  23. }
  24. public void setF(final float f) {
  25. this.f = f;
  26. }
  27. public Date getDate() {
  28. return this.date;
  29. }
  30. public void setDate(final Date date) {
  31. this.date = date;
  32. }
  33. public static void main(final String[] args) {
  34. TestAviator foo = new TestAviator(100, 3.14f, new Date());
  35. Map<String, Object> env = new HashMap<String, Object>();
  36. env.put("foo", foo);
  37. System.out.println(AviatorEvaluator.execute("'foo.i = '+foo.i", env));
  38. System.out.println(AviatorEvaluator.execute("'foo.f = '+foo.f", env));
  39. System.out.println(AviatorEvaluator.execute("'foo.date.year = '+ (foo.date.year+1990)", env));
  40. }
  41. }

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

引用变量

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

  1. 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 方法即可:

  1. System.out.println(AviatorEvaluator.execute("foo.i = 100; foo.f + foo.i", env));

这里的例子我们定义的都是脚本范围内的全局变量,局部变量和作用域参见下一节《3.5 作用域

访问 Java 静态变量

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

  1. ## examples/static_vars.av
  2. p("Math.PI is: " + Math.PI);
  3. use com.googlecode.aviator.AviatorEvaluator;
  4. p("AviatorEvaluator.VERSION is: " + AviatorEvaluator.VERSION);
  5. p("AviatorEvaluator.COMPILE is: " + AviatorEvaluator.COMPILE);

尝试执行将输出:

  1. Math.PI is: 3.141592653589793
  2. AviatorEvaluator.VERSION is: 5.1.5-SNAPSHOT
  3. AviatorEvaluator.COMPILE is: 0

对于 java.lang 下面的类可以直接访问,比如 Math 等,但是对于其他包下面的类,都要求先用 use 语法导入该类到当前上下文,然后通过 Class.Var 的方式访问,比如上面例子中的 AviatorEvaluator.VERSION ,不能通过完整类名的方式来访问,这将报错:

  1. p(com.googlecode.aviator.AviatorEvaluator.VERSION);
  1. Exception in thread "main" com.googlecode.aviator.exception.ExpressionRuntimeException: Could not find variable com.googlecode.aviator.AviatorEvaluator.VERSION
  2. at com.googlecode.aviator.runtime.type.AviatorJavaType.getProperty(AviatorJavaType.java:442)
  3. at com.googlecode.aviator.runtime.type.AviatorJavaType.getValueFromEnv(AviatorJavaType.java:329)
  4. at com.googlecode.aviator.runtime.type.AviatorJavaType.getValue(AviatorJavaType.java:317)
  5. at com.googlecode.aviator.runtime.function.system.PrintlnFunction.call(PrintlnFunction.java:54)
  6. at Script_1605786165591_47/1644443712.execute0(static_vars.av:11)
  7. at com.googlecode.aviator.ClassExpression.executeDirectly(ClassExpression.java:65)
  8. at com.googlecode.aviator.BaseExpression.execute(BaseExpression.java:136)
  9. at com.googlecode.aviator.Main.main(Main.java:45)
  10. Caused by: java.lang.NullPointerException: com
  11. at com.googlecode.aviator.runtime.type.AviatorJavaType.fastGetProperty(AviatorJavaType.java:542)
  12. at com.googlecode.aviator.runtime.type.AviatorJavaType.getProperty(AviatorJavaType.java:433)
  13. ... 7 more

特殊变量

AviatorScript 内置了部分特殊变量(启用了 Feature.InternalVars 才可以访问):

  • __exp__ (注意是双下划线),当前表达式的 Expression 对象。
  • __env__ 当前上下文环境 env
  • __instance__ 当前执行引擎 AviatorEvaluatorInstance 实例
  • __args__ 当前函数调用的参数列表。

我们可以简单打印看下 __env__ 看下:

  1. AviatorEvaluator.execute("let a = 1; p(__env__)");

输出:

  1. com.googlecode.aviator.utils.Env@7cca494b{
  2. __instance__=com.googlecode.aviator.AviatorEvaluatorInstance@7ba4f24f,
  3. __exp__=Script_1596184736312_0/1149319664@3b9a45b3,
  4. __env__=<this>,
  5. a=1
  6. }

可以看到所有的变量包括内部特殊变量,都可以在 __env__ 里找到。

外部变量(未初始化全局变量)

从编译后的 Expression 对象,可以获取脚本中未初始化的全局变量列表,通过 getVariableNames 方法:

  1. Expression expression = AviatorEvaluator.compile("b + a", true);
  2. List<String> vars = expression.getVariableNames();

vars 列表将是 ba ,更复杂一点的例子:

  1. Expression exp = AviatorEvaluator
  2. .compile("b=2; if(a > 1) { a + b } elsif( a > 10) { return a + c; } else { return 10; }");
  3. List<String> vars = exp.getVariableNames();

上面这个 vars 列表将是 [a, c] 。该方法可以识别出所有脚本中(哪怕是嵌套循环或者闭包)里的未初始化全局变量。

如果你的全局变量名称可能是 a.b.c 这样的命名,那么可以使用 getVariableFullNames() 来获取完整命名,它和 getVariableNames 区别就是会包括了含有 . 的变量名。