AviatorScript
# 一、介绍
Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。Aviator相比于Groovy、JRuby的笨重,Aviator非常轻量。
# 二、原理和特点
高性能:Aviator 的基本过程是将表达式直接翻译成对应的 java 字节码执行,整个过程最多扫两趟(开启执行优先模式,如果是编译优先模式下就一趟),这样就保证了它的性能超越绝大部分解释性的表达式引擎
轻量级:除了依赖
commons-``beanutils
这个库之外(用于做反射)不依赖任何第三方库,因此整体非常轻量级,整个 jar 包大小哪怕发展到现在 5.0 这个大版本,也才 430K一些比较有特色的特点:
- 支持运算符重载
- 原生支持大整数和 BigDecimal 类型及运算,并且通过运算符重载和一般数字类型保持一致的运算方式。
- 原生支持正则表达式类型及匹配运算符
=~
- 原生支持正则表达式类型及匹配运算符
- 类 clojure 的 seq 库及 lambda 支持,可以灵活地处理各种集合
开放能力:包括自定义函数接入以及各种定制选项
# 三、Hello World
# 1、Java使用
依赖引入
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.3</version>
</dependency>
案例
public class RunScriptExample {
public static void main(String[] args) throws IOException {
// Enable java method invocation by reflection.
AviatorEvaluator.getInstance()
.setFunctionMissing(JavaMethodReflectionFunctionMissing.getInstance());
// You can trry to test every script in examples folder by changing the file name.
Expression exp = AviatorEvaluator.getInstance().compileScript("/Users/10072656/IdeaProjects/spring-demo/src/main/java/com/example/springdemo/examples/hello.av");
exp.execute();
}
}
# 2、编译和执行
# 2.1、AviatorScript引擎
AviatorScript编译和执行的入口都是AviatorEvaluator
类,该类的一个实例就是一个编译和执行的单元,这个单元我们称为一个 AviatorScript 引擎,AviatorEvaluator.getInstance()
返回一个全局共享的 AviatorScript 引擎。
# 2.2、编译脚本文件
用 compileScript(path)
方法编译一个脚本文件,这个方法对文件路径查找按照下列顺序:
- path 指定的文件系统绝对或者相对路径
user.dir
项目的根目录开始的相对路径- classpath 下的绝对和相对路径
找到就尝试读取脚本并动态实时地编译成 JVM 字节码,最终的结果是一个 Expression
对象,每次调用 compileScript(path)
都生成一个新的匿名类和对象,因此如果频繁调用会占满 JVM 的 metaspace,可以通过第二个参数决定是否缓冲:
Expression exp = AviatorEvaluator.getInstance().compileScript("examples/hello.av", true);
exp.execute();
# 2.3、编译文本文件
如果我们脚步存储在其他地方,比如数据库、外部文件等,获取后得到的是一个String对象,我们可以使用comiple
方法来编译:
Expression compile = AviatorEvaluator.getInstance().compile("println('Hello, AviatorScript!');");
compile.execute();
# 2.4、执行
编译产生的 Expression
对象,最终都是调用 execute()
方法执行,得到结果。但是 execute
方法还可以接受一个变量列表组成的 map,来注入执行的上下文,我们来一个例子:
public class RunAcExample {
public static void main(String[] args) {
String expression = "a + b > 100";
Expression compile = AviatorEvaluator.compile(expression);
Object execute = compile.execute(compile.newEnv("a", 80, "b", 70));
System.out.println(execute);
}
}
我们定义了一个表达式,但是这个表达式的参数具体数值没有指定,我们可以在执行的时候指定具体的数值。
# 2.5、语法校验
AviatorEvaluator.validate("1 ++ 2");
# 四、基本类型和语法
# 1、基本类型及运算
# 1.1、整数
整数例如 -99、0、1、2、100……等等,对应的类型是 java 中的 long 类型。AviatorScript 中并没有 byte/short/int 等类型,统一整数类型都为 long,支持的范围也跟 java 语言一样。
let a = 99;
let b = 0xFF;
let c = -99;
println(a + b);
println(a / b);
println(a- b + c);
println(a + b * c);
println(a- (b - c));
println(a/b * b + a % b);
public class Number {
public static void main(String[] args) throws IOException {
Expression expression = AviatorEvaluator.getInstance().compileScript("src/main/java/com/example/springdemo/examples/number_demo/number.av");
Object execute = expression.execute();
System.out.println(execute);
}
}
整数相除的结果仍然是整数,比如例子中的
**a/b**
结果就是 0,遵循 java 的整数运算规则。
# 1.2、大整数
let a = 10223372036854774807; ## Literal BigInteger
let b = 1000N; ## BigInteger
let c = 1000; ## long type
println(a);
println(a + a);
println(a * a);
println(a + b + c);
# 1.3、浮点数
数字除了整数之外,AviatorScript 同样支持浮点数,但是仅支持 double 类型,也就是双精度 64 位,符合 IEEE754 规范的浮点数。传入的 java float 也将转换为 double 类型。所有的浮点数都被认为是 double 类型。浮点数的表示形式有两种:
十进制的带小数点的数字,比如
1.34159265
,0.33333
等等。科学计数法表示,如
1e-2
,2E3
等等,大小写字母e
皆可。
let a = 2;
let err = 1e-15;
let root = a;
while math.abs(a - root * root) > err {
root = (a/root + root) / 2.0;
}
println("square root of 2 is: " + root);
# 1.4、高精度计算
浮点数是无法用于需要精确运算的场景,比如货币运算或者物理公式运算等,这种情况下如果在 Java 里一般推荐使用 BigDecimal
类型,调用它的 add/sub 等方法来做算术运算。
AviatorScript 将 BigDecimal
作为基本类型来支持(下文简称 decimal 类型),只要浮点数以 M
结尾就会识别类型为 deicmal,例如 1.34M
、 0.333M
或者科学计数法 2e-3M
等等。
let a = 2M;
let err = 1e-15M;
let root = a;
while math.abs(a - root * root) > err {
root = (a/root + root) / 2.0M;
}
println("square root of 2M is: " + root);
# 1.5、数字类型转换
数字类型在运算的时候,会遵循一定的类型转换规则:
- 单一类型参与的运算,结果仍然为该类型,比如整数和整数相除仍然是整数,double 和 double 运算结果还是 double。
- 多种类型参与的运算,按照下列顺序:
long -> bigint -> decimal -> double
自动提升,比如 long 和 bigint 运算结果为 bigint, long 和 decimal 运算结果为 decimal,任何类型和 double 一起运算结果为 double
let a = 1;
let b = 2;
println("a/b is " + a/b);
println("a/double(b) is " + a/double(b));
a/b is 0
a/double(b) is 0.5
# 1.6、字符串
基本使用
let a = "hello world";
println(a);
println(string.length(a));
// hello world
// 11
let a = "hello world";
let b = 'AviatorScript';
println(a);
println(string.length(a));
println(a + ',' + b + 5); // hello world,AviatorScript5
转义
println('Dennis\'s car');
println('AviatorScript is great.\r\nLet\'s try it!');
// Dennis's car
// AviatorScript is great.
// Let's try it!
字符串赋值
let name = "aviator";
let s = "hello," + name; // hello,aviator
let name = "aviator";
let a = 1;
let b = 2;
let s = "hello, #{name}, #{a} + #{b} = #{a + b}"; // hello, aviator, 1 + 2 = 3
p(s);
# 1.7、布尔类型
println("3 > 1 is " + (3 > 1));
println("3 >= 1 is " + (3 >= 1));
println("3 >= 3 is " + (3 >= 3));
println("3 < 1 is " + (3 < 1));
println("3 <= 1 is " + (3 <= 1));
println("3 <= 3 is " + (3 <= 3));
println("3 == 1 is " + (3 == 1));
println("3 != 1 is " + (3 != 1));
// 3 > 1 is true
// 3 >= 1 is true
// 3 >= 3 is true
// 3 < 1 is false
// 3 <= 1 is false
// 3 <= 3 is true
// 3 == 1 is false
// 3 != 1 is true
# 1.8、逻辑运算
布尔值可参于逻辑与、逻辑或、逻辑否等运算,假设 x 和 y 的返回结果是布尔值:
x && y 表示并且的关系,x 为真,并且 y 为真的情况下,结果为 true,否则 false。
x || y 表示或者的关系, x 为真,或者 y 为真,结果就为 true,两者都为假值的时候结果为 false。
!x 否定运算符,如果 x 为 true,则结果为 false,反之则为 true。
&& 和 || 都支持短路规则:
如果 x 为假值, x && y 直接返回 false, y 就不进行求值。
如果 x 为真值, x || y 直接返回 true, y 也不进行求值。
# 1.9、三元运算符
let a = 3;
let b = 1;
let c = a > b ? println("a > b") : println("a <= b");
println(c);
// a > b
// null
# 1.10、正则表达式
let p = /^(.*)\.av$/;
println(p == p); ## print true
println("regexp.av" =~ p); ##print true
# 2、运算符
# 2.1、幂运算
引入幂运算符 **
,原来使用 math.pow(a, b)
的代码都可以写成 a**b
,幂运算符的优先级较高,在单目运算符之上。
p(2 ** 3);
p(2 ** -3);
p(2N ** 3);
p(2M ** 3);
p(2 ** 2.2);
//8
//0.125
//8
//8
//4.59479341998814
# 2.2、位运算
● & 位与运算
● | 或运算
● ^ 异或运算
● << 左移运算
● >> 右移运算
● >>> 无符号右移
# 3、注释
## 这是一行注释
a = 1;
a = 1; ## 行尾注释
AviatorScript 仅支持
##
单行注释,如果你需要多行,可以连续使用
# 4、变量
# 4.1、定义和赋值
AviatorScript 中变量的定义和赋值是不可分割的,定义一个变量的同时需要给他赋值
a = 1;
println(type(a));
s = "Hello AviatorScript";
println(type(s));
# 4.2、动态类型
AviatorScript 是动态类型语言,变量的类型随着赋值而改变
s = "Hello AviatorScript";
println(type(s));
s = 99;
println(type(s));
s 原来是一个字符串,我们通过赋值 s = 99
,他的类型变为了数字,就可以参与算术运算。
# 4.3、nil
当我们想表示一个变量还没有赋值的时候,需要用到 nil
这个特殊类型,它和 java 中的 null
作用一样,表示当前变量还没有赋值。nil 最常见的场景就是用于判断变量是否为 null:
a = nil;
b = 99;
if a == nil {
println("a is " + type(a));
}
a = 3;
if a !=nil {
println(a + b);
}
# 4.4、传入变量
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);
如果脚本中用到的变量没有传入,并且没有定义,那么默认值将是 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);
# 4.5、引用变量
对于深度嵌套并且同时有数组的变量访问,例如 foo.bars[1].name
AviatorEvaluator.execute("'hello,' + #foo.bars[1].name", env)
引用变量要求以 #
符号开始,变量如果包含特殊字符串,需要使用两个 ` 符号来包围,并且变量名中不能包含其他变量。
# 4.6、访问Java静态变量
p("Math.PI is: " + Math.PI);
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
# 5、作用域
# 5.1、let语句
let 语句就是让你在特定作用域内定义一个变量,如果父作用域有同名的变量,将“掩盖”父作用域的变量。
let a = 1; ## 全局作用域内的 a
{
## 嵌套作用域 scope1
a = 2; ## 不适用 let,访问的还是全局作用域的变量 a
println(a); ##打印 scope1 内的 a
a = 3; ##给全局作用域的变量 a 赋值
}
println(a); ## 打印全局作用域的 a
// 2
// 3
# 5.2、嵌套作用域
作用域还可以继续深层嵌套,遵循的规则不变:
let
定义当前作用域的变量,这些变量同时可以被它的子作用域访问和修改,离开当前作用域后不可触达。let
定义的变量将“掩盖”父作用域的同名变量。子作用域可以访问和修改父作用域定义的变量,离开子作用域后修改继续生效
let a = 1;
{
a = 2;
let a = 3;
b = 4;
{
a = 5;
b = 6;
let c = 7;
println("a in scope2:" + a);
println("b in scope2:" + b);
println("c in scope2:" + c);
}
println("a in scope1:" + a);
println("b in scope1:" + b);
println("c in scope1:" + c);
}
println("a in global scope:" + a);
println("b in global scope:" + b);
println("c in global scope:" + c);
// a in scope2:5
// b in scope2:6
// c in scope2:7
// a in scope1:5
// b in scope1:6
// c in scope1:null
// a in global scope:2
// b in global scope:null
// c in global scope:null
# 6、多行表达式和return
# 6.1、多行表达式
let a = 1;
let b = 2;
c = a + b;
整个脚本的返回结果默认是最后一个表达式的结果。但是这里需要注意的是,加上分号后,整个表达式的结果将固定为 nil,因此如果你执行上面的脚本,并打印结果,一定是 null,而不是 c 的值。
如果你想返回表达式的值,而不是为 nil,最后一个表达式不加分号即可:
let a = 1;
let b = 2;
c = a + b
# 6.2、return
除了不加分号来返回之外,你也可以用 return 语句来指定返回:
let a = 1;
let b = 2;
c = a + b;
return c;
return 也用于提前返回,结合条件语句可以做更复杂的逻辑判断:
if a < b {
return "a is less than b.";
}
return a - b;
# 7、创建对象
let d = new java.util.Date();
p(type(d));
p(d);
let year = getYear(d);
let month = getMonth(d);
p("Year is: " + year);
p("Month is: " + month);
// java.util.Date
// Thu Apr 23 11:25:52 CST 2020
// Year is: 120
// Month is: 3
# 8、use语句引用Java类
aviatorscript 支持 use 语句,类似 java 里的 import 语句,可以导入 java 类到当前命名空间,减少在 new 或者 try...catch 等语句里写完整包名的累赘方式。
use java.util.*;
let list = new ArrayList(10);
seq.add(list, 1);
seq.add(list, 2);
p("list[0]=#{list[0]}");
p("list[1]=#{list[1]}");
let set = new HashSet();
seq.add(set, "a");
seq.add(set, "a");
p("set type is: " + type(set));
p("set is: #{set}");
# 五、条件语句
if(false) {
println("in if body");
} else {
println("in else body");
}
# 六、循环语句
# 1、普通循环
for i in range(0, 10) {
println(i);
}
我们可以指定增长的步调:
for i in range(0, 10, 2) {
println(i);
}
# 2、集合遍历
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);
// a=1
// b=2
// c=3
// sum of c is 45
# 3、索引和KV遍历
let a = tuple(1, 2, 3, 4, 5, 6, 7, 8, 9);
for i, x in a {
assert(i + 1 == x);
p("a[#{i}] = #{x}");
}
# 4、continue/break/return
for i in range(0, 10) {
if i % 2 == 0 {
continue;
}
println(i);
}
for i in range(0, 10) {
if i > 5 {
break;
}
println(i);
}
# 5、while
let sum = 1;
while sum < 1000 {
sum = sum + sum;
}
println(sum);
# 七、Statement语句和值
# 1、条件语句的值
let a = if (true) {
1
};
p("a is :" + type(a) +", " + a);
// a is :long, 1
let a = if (false) {
1
};
p("a is :" + type(a) +", " + a);
// a is :nil, null
let a = if (false) {
1
} else {
2
};
p("a is :" + type(a) +", " + a);
// a is :long, 2
# 2、循环语句的值
let b = for x in range(0, 10) {
x
};
p("b is :" + type(b) +", " + b);
循环语句的结果是最后一次迭代过程中返回的值,因此这里是最后一次迭代 x 的值,也就是 9
:
b is :long, 9
let b = for x in range(0, 10) {
if x == 2 {
break;
}
};
p("b is :" + type(b) +", " + b);
// b is :nil, null
let b = for x in range(0, 10) {
if x == 2 {
return x;
}
};
p("b is :" + type(b) +", " + b);
// b is :long, 2
# 3、块(Block)的值
let c = {
let a = 1;
let b = 2;
a + b
};
p("c is :" + type(c) +", " + c);
// c is :long, 3
let c = {
let a = 1;
let b = 2;
if a > b {
return a;
} else {
return b;
}
};
p("c is :" + type(c) +", " + c);
// c is :long, 2
# 八、异常处理
try {
throw "an exception";
} catch(e) {
pst(e);
} finally {
p("finally");
}
- throw 抛出了一个字符串,在 AviatorScript 中,可以 **throw 任何东西,**非异常的对象都将被转化包装为标准错误 com.googlecode.aviator.exception.StandardError 类的实例。
catch(e)
没有指定异常的类型, AviatorScript 允许不指定异常类型,等价于catch(Throwable e)
。pst(e)
用于打印异常堆栈,也就是e.printStackTrace()
调用。- AviatorScript 中同样支持
finally
语句,这跟 Java 保持一致。
# 九、函数和闭包
# 1、函数定义和调用
fn add(x, y) {
return x + y;
}
three = add(1, 2);
println(three); // 3
s = add('hello', ' world');
println(s); // hello world
# 2、函数返回值
fn add(x, y) {
if type(x) != 'long' || type(y) != 'long' {
throw "unsupported type";
}
x + y
}
println(add(1, 2)); // 3
println(add('hello', ' world')); // unsupported type错误
# 3、函数重载
fn join(s1) {
"#{s1}"
}
fn join(s1, s2) {
"#{s1}#{s2}"
}
fn join(s1, s2, s3) {
"#{s1}#{s2}#{s3}"
}
p(join("hello"));
p(join("hello", " world"));
p(join("hello", " world", ", aviator"));
// hello
// hello world
// hello world, aviator
// null
# 4、不定参数
fn join(sep, &args) {
let s = "";
let is_first = true;
for arg in args {
if is_first {
s = s + arg;
is_first = false;
}else {
s = s + sep + arg;
}
}
return s;
}
p(join(" ", "a", "b", "c"));
p(join(",", "a", "b", "c", "d"));
p(join(",", "a"));
// a b c
// a,b,c,d
// a
# 5、匿名函数
匿名函数的基本定义形式是:
lambda (参数1,参数2...) -> 参数体表达式 end
let add = lambda (x,y) ->
x + y
end;
three = add(1, 2);
println(three); // 3
# 6、闭包
let counter = lambda() ->
let c = 0;
lambda() ->
let result = c;
c = c + 1;
return result;
end
end;
let c1 = counter();
let c2 = counter();
println("test c1...");
for i in range(0, 10) {
x = c1();
println(x);
}
println("test c2...");
for i in range(0, 10) {
x = c2();
println(x);
}
c 在函数 counter
中定义,理论上说局部变量在 counter 函数返回后就“销毁”了,但是匿名的结果函数却“捕获”(closure over)了局部变量 c,哪怕在 counter
返回后,仍然可以继续访问到变量 c
,这就称之为闭包(closure), c 就是所谓自由变量。
# 十、数组
# 1、创建数组
tuple 函数可以创建一个固定大小的数组,等价 java 的类型为 Object [] :
let t = tuple(1, 2, "hello", 3.14);
println("type of t: " + type(t));
for x in t {
println(x);
}
println("count of t: "+ count(t));
println("t[0] = " + t[0]);
t[0] = 100;
println("t[0] = " + t[0]);
// type of t: Object[]
// 1
// 2
// hello
// 3.14
// count of t: 4
// t[0] = 1
// t[0] = 100
# 2、创建指定类型数组
let a = seq.array(int, 1, 2, 3, 4);
println("type(a) is : " + type(a));
println("count(a) is: " + count(a));
这样如果插入一个错误类型,就会报错。
# 3、创建空数组
# 3.1、一维数组
let a = seq.array_of(int, 3);
println("type(a) is : " + type(a));
println("count(a) is: " + count(a));
println("before assignment:");
for x in a {
println(x);
}
for i in range(0, 3) {
a[i] = i;
}
println("after assignment:");
for x in a {
println(x);
}
// type(a) is : int[]
// count(a) is: 3
// before assignment:
// 0
// 0
// 0
// after assignment:
// 0
// 1
// 2
# 3.2、多维数组
let a = seq.array_of(long, 3, 2);
assert(3 == count(a));
assert(2 == count(a[0]));
let x = 0;
for i in range(0, 3) {
for j in range(0, 2) {
a[i][j] = x;
x = x + 1;
}
}
for i in range(0, 3) {
for j in range(0, 2) {
p("a[#{i}][#{j}] = #{a[i][j]}");
}
}
// a[0][0] = 0
// a[0][1] = 1
// a[1][0] = 2
// a[1][1] = 3
// a[2][0] = 4
// a[2][1] = 5
# 4、遍历数组
let a = seq.array(int, 1, 2, 3.3, 4);
for x in a {
println(x);
}
# 十一、集合
# 1、List
创建一个链表可以通过 seq.list
函数:
let list = seq.list(1, 2, 3);
上面将创建三个整数组成的 ArrayList 对象, seq.list 接受不定参数,如果不传入任何参数,创建的是一个空链表:
let empty_list = seq.list();
链表和数组类似,也可以通过 a[i] = x
的方式来赋值,前提是 i
落在长度内:
let list = seq.list(1, 2, 3);
list[0] = 4;
list[1] = 5;
list[2] = 6;
println(list);
# 2、Map
创建一个 HashMap
也很容易,使用 seq.map(k1, v1, k2, v2 ...)
的方式:
let m = seq.map("a", 1, "b", 2, "c", 3, 4, 5);
println(m);
对于 map ,你可以用 m.{key}
的方式来访问:
println("m.a = " + m.a);
println("m.b = " + m.b);
println("m.c = " + m.c);
如果要获取 key 的集合,可以用 seq.keys(m)
函数, value 集合是用 seq.vals
函数:
p("key set: " + seq.keys(m));
p("value set: " + seq.vals(m));
# 3、Set
创建不重复的元素组成的集合 Set,可以用 seq.set
:
let s = seq.set(1, 2, 2, "hello", 3.3, "hello");
println(s); // [1, 2, hello, 3.3]
println("type(s) is: " + type(s)); // type(s) is: java.util.HashSet
Set 最常见的操作是判断某个元素是否存在,可以用 include
函数:
println(include(s, 1));
println(include(s, "hello"));
println(include(s, 100));
# 4、集合操作
# 4.1、添加元素
let list = seq.list();
let set = seq.set();
let map = seq.map();
## add elements
for i in range(0, 3) {
seq.add(list, i);
seq.add(set, i);
seq.add(map, i, i);
}
println("list: " + list);
println("set: " + set);
println("map: " + map);
// list: [0, 1, 2]
// set: [0, 1, 2]
// map: {0=0, 1=1, 2=2}
# 4.2、访问元素
访问集合中的元素可以用 seq.get(coll, key) 函数,它同时支持数组和所有集合类型:
- 对于数组和链表, key 就是 0~(len - 1) 的索引位置整数,返回的是该位置的值,超过范围内的访问将抛出异常。
- 对于 map 来说,key 就是键值对的 key,返回的是对应的 value。
- 对于 set 来说,key 就是集合里的元素,如果存在,返回该 key 本身,不存在返回 nil。
for i in range(0, 3) {
assert(i == seq.get(list, i));
assert(i == seq.get(set, i));
assert(i == seq.get(map, i));
}
println("seq.get(set, 3) is: " + seq.get(set, 3)); ## nil
# 4.3、判断元素是否存在
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));
# 4.4、遍历集合
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);
}
# 4.5、删除元素
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);