这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
变量
定义和赋值
和其他语言类似, 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(x)); }
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 <JavaType, c, null, null> with <Long, 3>
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` 这样的访问,如果有此类特殊需求,请通过[自定义函数](https://www.yuque.com/boyan-avfmj/aviatorscript/xbdgg2)实现。
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 作用域](https://www.yuque.com/boyan-avfmj/aviatorscript/lhht36)》
## 访问 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=, a=1 }
可以看到所有的变量包括内部特殊变量,都可以在 `__env__` 里找到。
## 外部变量(未初始化全局变量)
从编译后的 `Expression` 对象,可以获取脚本中未初始化的全局变量列表,通过 `getVariableNames` 方法:
Expression expression = AviatorEvaluator.compile("b + a", true); List 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 vars = exp.getVariableNames();
上面这个 `vars` 列表将是 `[a, c]` 。该方法可以识别出所有脚本中(哪怕是嵌套循环或者闭包)里的未初始化全局变量。
如果你的全局变量名称可能是 `a.b.c` 这样的命名,那么可以使用 `getVariableFullNames()` 来获取完整命名,它和 `getVariableNames` 区别就是会包括了含有 `.` 的变量名。