AviatorScript 支持常见的类型,如数字、布尔值、字符串等等,同时将大整数、BigDecimal、正则表达式也作为一种基本类型来支持。

首先介绍下数字。

数字

数字包括整数和浮点数,AviatorScript 对 java 的类型做了缩减和扩展,同时保持了一致的运算符规则。

整数和算术运算

整数例如 -99、0、1、2、100……等等,对应的类型是 java 中的 long 类型。AviatorScript 中并没有 byte/short/int 等类型,统一整数类型都为 long,支持的范围也跟 java 语言一样:-9223372036854774808~9223372036854774807。

整数也可以用十六进制表示,以 0x 或者 0X 开头的数字,比如 0xFF(255)、0xAB(171) 等等。

整数可以参与所有的算术运算,比如加减乘除和取模等等。

  1. let a = 99;
  2. let b = 0xFF;
  3. let c = -99;
  4. println(a + b);
  5. println(a / b);
  6. println(a- b + c);
  7. println(a + b * c);
  8. println(a- (b - c));
  9. println(a/b * b + a % b);

加减乘除对应的运算符就是 +,-,*,/ 这都比较好理解,取模运算符就是 % ,规则和语法和 java 是一样的。

需要注意,整数相除的结果仍然是整数,比如例子中的 **a/b** 结果就是 0,遵循 java 的整数运算规则。

运算符之间的优先级如下:

  • 单目运算符 - 取负数
  • *, /
  • +,-

整个规则也跟 java 的运算符优先级保持一致。你可以通过括号来强制指定优先级,比如例子中的 a-(b-c) 就是通过括号,强制先执行 b-c ,再后再被 a 减。

通常来说,复杂的算术表达式,从代码可读性和稳健角度,都推荐使用括号来强制指定优先级。

大整数(BigInt)

对于超过 long 的整数, AviatorScript 还特别提供了大整数类型的支持,对应 java.math.BigInteger 类。任何超过 long 范围的整数字面量,会自动提升为 BigInteger 对象(以下简称 BigInt),任何数字以 N 字母结尾就自动变 BigInt:

  1. ## examples/bigint.av
  2. let a = 10223372036854774807; ## Literal BigInteger
  3. let b = 1000N; ## BigInteger
  4. let c = 1000; ## long type
  5. println(a);
  6. println(a + a);
  7. println(a * a);
  8. println(a + b + c);

10223372036854774807 是一个远远超过 long 返回的数字,b 也是 BigInt 类型,因为它以 N 结尾,BigInt 的算术运算和一般整数没有什么两样,采用同样的算术运算符和规则,执行这段脚本将打印

  1. 10223372036854774807
  2. 20446744073709549614
  3. 104517335803944147014652834074681887249
  4. 10223372036854776807

请注意,默认的 long 类型在计算后如果超过范围溢出后,不会自动提升为 BigInt,但是 BigInt 和 long 一起参与算术运算的时候,结果为 BigInt 类型。关于类型转换的规则,我们后面再详细介绍。

浮点数

数字除了整数之外,AviatorScript 同样支持浮点数,但是仅支持 double 类型,也就是双精度 64 位,符合 IEEE754 规范的浮点数。传入的 java float 也将转换为 double 类型。所有的浮点数都被认为是 double 类型。浮点数的表示形式有两种:

  1. 十进制的带小数点的数字,比如 1.341592650.33333 等等。
  2. 科学计数法表示,如 1e-22E3 等等,大小写字母 e 皆可。

看一个简单例子,牛顿法求平方根

  1. ## examples/square_root.av
  2. let a = 2;
  3. let err = 1e-15;
  4. let root = a;
  5. while math.abs(a - root * root) > err {
  6. root = (a/root + root) / 2.0;
  7. }
  8. println("square root of 2 is: " + root);

这个例子稍微复杂了一点,因为我们用了后面才会讲到的 while 循环语句(参见条件语句),不过整体逻辑还是比较简单的,求 2 的平方根,我们通过不停计算 (a/root + root)/2.0 的值,看看是否在误差范围( err 指定)内,不在就继续迭代计算,否则就跳出循环打印结果:

  1. square root of 2 is: 1.414213562373095

浮点数的运算符跟整数一样,同样支持加减乘除,优先级也是一样。浮点数和浮点数的算术运算结果为浮点数,浮点数和整数的运算结果仍然为浮点数。

高精度计算(Decimal)

浮点数是无法用于需要精确运算的场景,比如货币运算或者物理公式运算等,这种情况下如果在 Java 里一般推荐使用 BigDecimal 类型,调用它的 add/sub 等方法来做算术运算。

AviatorScript 将 BigDecimal 作为基本类型来支持(下文简称 decimal 类型),只要浮点数以 M 结尾就会识别类型为 deicmal,例如 1.34M0.333M 或者科学计数法 2e-3M 等等。

decimal 同样使用 +,-,*,/ 来做算术运算, AviatorScript 重载了这些运算符的方法,自动转成 BigDecimal 类的各种运算方法。我们把求平方根的例子改成 decimal 运算

  1. ## examples/bigdecimal.av
  2. let a = 2M;
  3. let err = 1e-15M;
  4. let root = a;
  5. while math.abs(a - root * root) > err {
  6. root = (a/root + root) / 2.0M;
  7. }
  8. println("square root of 2M is: " + root);

运算结果 root 的类型也是 decimal 。除了 double 以外的数字类型和 decimal 一起运算,结果为 decimal。任何有 double 参与的运算,结果都为 double。

默认运算精度是 MathContext.DECIMAL128 ,你可以通过修改引擎配置项 Options.MATH_CONTEXT 来改变。

如果你觉的为浮点数添加 M 后缀比较麻烦,希望所有浮点数都解析为 decimal ,可以开启 Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL 选项。

数字类型转换

数字类型在运算的时候,会遵循一定的类型转换规则:

  • 单一类型参与的运算,结果仍然为该类型,比如整数和整数相除仍然是整数,double 和 double 运算结果还是 double。
  • 多种类型参与的运算,按照下列顺序: long -> bigint -> decimal -> double 自动提升,比如 long 和 bigint 运算结果为 bigint, long 和 decimal 运算结果为 decimal,任何类型和 double 一起运算结果为 double

你可以通过 long(x) 将一个数字强制转化为 long,这个过程中可能丢失精度,也可以用 double(x) 将一个数字强转为 double 类型。

  1. ## examples/double.av
  2. let a = 1;
  3. let b = 2;
  4. println("a/b is " + a/b);
  5. println("a/double(b) is " + a/double(b));

a 和 b 都是 long 类型,他们相除的结果仍然是整数, 1/2 结果为 0,但是当使用 double 函数将 b 强制转为 double 类型,两者的结果就是浮点数了:

  1. a/b is 0
  2. a/double(b) is 0.5

字符串

在任何语言中,字符串都是最基本的类型,比如 java 里就是 String 类型。AviatorScript 中同样支持字符串,只要以单引号或者双引号括起来的连续字符就是一个完整的字符串对象,例如:

  • "hello world"
  • 'hello world'
  • "a" 或者 'a'

字符串可以直接通过 println 函数打印。

字符串的长度可以通过 string.length 函数获取:

  1. ## examples/string.av
  2. let a = "hello world";
  3. println(a);
  4. println(string.length(a));

打印:

  1. hello world
  2. 11

字符串拼接可以用 + 号(这又是一个运算符重载):

  1. ## examples/string.av
  2. let a = "hello world";
  3. let b = 'AviatorScript';
  4. println(a);
  5. println(string.length(a));
  6. println(a + ',' + b + 5);

字符串拼接 a + ',' + b + 5 包括了数字 5 和字符串 ',' , 任何类型和字符串相加,都将拼接为字符串,这跟 java 的规则一致。因此上面最后一行代码将打印 hello world,AviatorScript5

字符串还包括其他函数,如截取字符串 substring,都在 string 这个 namespace 下,具体见函数库列表

转义

同样,和其他语言类似,遇到特殊字符,AviatorScript 中的字符串也支持转义字符,和 java 语言一样,通过 \ 来转义一个字符,比如我们想表示的字符串中有单引号,如果我们继续使用单引号来表示字符串,这时候就需要用到转义符:

  1. ## examples/escape_string.av
  2. println('Dennis\'s car');
  3. println('AviatorScript is great.\r\nLet\'s try it!');

特殊字符,比如 \r\n\t 等也是同样支持。上述例子我们使用了换行 \r\n ,将打印:

  1. Dennis's car
  2. AviatorScript is great.
  3. Let's try it!

当然,针对引号这个情况,这里你可以简单用双引号来表示字符串,就可以避免转义:

  1. println("Dennis 's car");

字符串插值(String Interpolation)

字符串拼接可以用加法,比如

  1. let name = "aviator";
  2. let s = "hello," + name;

拼接后的字符串 s 就是 hello,aviator+ 加法对字符串拼接做了特别优化,内部会自动转化成 StringBuilder 来做拼接。但是对于更复杂的场景,字符串拼接的语法仍然显得过于丑陋和繁琐,因此 5.1.0 开始, AviatorScript 支持了字符串插值,一个例子:

  1. ## examples/string_interpolation.av
  2. let name = "aviator";
  3. let a = 1;
  4. let b = 2;
  5. let s = "hello, #{name}, #{a} + #{b} = #{a + b}";
  6. p(s);

字符串中 #{} 括起来的表达式都将在当前上下文里自动执行求值,然后插入到最终的结果字符串,上面的例子将输出:

  1. hello, aviator, 1 + 2 = 3

AviatorScript 内部做了大量优化,在编译模式复用 Expression 的情况下性能比使用加法拼接字符串更快。

布尔类型和逻辑运算

布尔类型用于表示真和假,它只有两个值 truefalse 分别表示真值和假值。

比较运算如大于、小于可以产生布尔值:

  1. ## examples/boolean.av
  2. println("3 > 1 is " + (3 > 1));
  3. println("3 >= 1 is " + (3 >= 1));
  4. println("3 >= 3 is " + (3 >= 3));
  5. println("3 < 1 is " + (3 < 1));
  6. println("3 <= 1 is " + (3 <= 1));
  7. println("3 <= 3 is " + (3 <= 3));
  8. println("3 == 1 is " + (3 == 1));
  9. println("3 != 1 is " + (3 != 1));

输出:

  1. 3 > 1 is true
  2. 3 >= 1 is true
  3. 3 >= 3 is true
  4. 3 < 1 is false
  5. 3 <= 1 is false
  6. 3 <= 3 is true
  7. 3 == 1 is false
  8. 3 != 1 is true

上面演示了所有的逻辑运算符:

  • > 大于
  • >= 大于等于
  • < 小于
  • <= 小于等于
  • == 等于
  • != 不等于

逻辑运算和短路规则

布尔值可参于逻辑与、逻辑或、逻辑否等运算,假设 xy 的返回结果是布尔值:

  • 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. ## examples/boolean.av
  2. let result = false && println('not reachable');
  3. println(result);
  4. let result = true || println('not reachable');
  5. println(result);

println 返回结果不是布尔值,如果参与执行逻辑运算会报错,但是由于短路规则, println 都不会执行,也就不会打印 not reachable ,直接返回 falsetrue

三元运算符

布尔值可用于三元表达式和条件语句作为判断,决定执行哪个分支代码。这里先介绍下三元表达式 test ? stmt1: stmt2 ,当 test 的执行结果为 true 的时候,执行 stmt1 ,反之则执行 stmt2 ,结果即为 stmt1 或者 stmt2 的执行结果:

  1. ## examples/ternary.av
  2. let a = 3;
  3. let b = 1;
  4. let c = a > b ? println("a > b") : println("a <= b");
  5. println(c);


将打印:

  1. a > b
  2. null

可见,只执行了 println("a > b") ,而 false 的分支确实没有执行。返回结果是 println 的返回结果 null

跟 java 不同的是 AviatorScript 允许两个分支返回的结果类型可以是不兼容的:

  1. ## examples/ternary.av
  2. let a = 3;
  3. let b = 1;
  4. let c = a > b ? "a > b" : 999;
  5. println(c);

a > b ? "a > b" : 999 两个分支的结果分别是字符串和数字,这在 AviatorScript 中是完全可以的,这里将返回字符串 a > b

正则表达式

AviatorScript 中正则表达式也是一等公民,作为基本类型来支持, / 括起来的正则表达式就是一个 java.util.Pattern 实例,例如 /\d+/ 表示 1 个或者多个数字,正则表达式语法和 java 完全相同,但是对于需要转义的字符不需要连续的反斜杠 \\ ,只要一个 \ 即可,比如我们要匹配 .av 为结尾的文件,正则可以写成 /^.*\.av$/ ,这里的 \. 来转义后缀里的 . 符号。

正则表达式能参与的运算只有比较运算符和正则匹配运算符 =~ :

  1. ## examples/regexp.av
  2. let p = /^(.*)\.av$/;
  3. println(p == p); ## print true
  4. println("regexp.av" =~ p); ##print true
  5. println("$0=" + $0);
  6. println("$1=" + $1);

我们定义了一个正则表达式 p,用于匹配以 .av 结尾的文件名,匹配是用 =~ 运算符,匹配运算符左侧是字符串,右侧是正则表达式,如果匹配成功,返回 true,否则返回 false。

这里 regexp.av 是匹配成功,因此打印 true , 如果匹配成功,同时 AviatorScript 会将正则中的匹配的分组放入 $0$1 , $2 … 的变量中,其中 $0 表示匹配的整个字符串,而 $1 表示第一个分组,以此类推。这里就是文件名,正则中用括号括起来的第一个分组 (.*)

因此上面将打印:

  1. true
  2. true
  3. $0=regexp.av
  4. $1=regexp

如果你不想自动捕获分组并放入美元符开头的变量,可以通过设置 Options.PUT_CAPTURING_GROUPS_INTO_ENV 为 false 来关闭此行为。