2. 变量、表达式及语句
2.1 概述
WTCD 允许定义并使用变量,运算表达式,执行语句。
2.2 值的类型
WTCD 有以下值类型:
number
:数字,支持正负和小数boolean
:布尔值,只能是true
或者false
string
:字符串null
:空值action
: 表示一个需要可执行的操作(goto
,exit
,或selection
)choice
:表示一个选项(其中记录了这个选项的文字和被选择后执行的操作)list
:存储一个列表(详见第 4 章)function
:存储一个函数(详见第 5 章)
本章节将会跳过 list
和 function
。
2.3 变量
WTCD 允许定义定义和使用变量。每个变量都有固定的类型,其中可使用的有上述每一个除了 null
以外的类型。
2.3.1 定义变量
若要定义变量,则需要使用 declare
语句 。
declare <declaration>
与其他语句不同的是,declare
语句除了可以出现在语句组(见 2.4.3)内以外,declare
还可以和 section
的逻辑块(见 3.2)平级。
<declaration>
可以是单个定义或是用方括号包裹的若干个定义。每个定义采用如下格式:
<变量类型> <变量名> [= <初始值>]
2.3.1.1 单一定义
举个例子:
declare number a = 100
declare boolean b = false
这两个 declaration
分别定义了一个类型为 number
,初始值为 100
的变量 a
,和一个类型为 boolean
,初始值为 false
的变量 b
。
2.3.1.2 多个定义
上述例子中的两个定义也可以合并成一个:
declare [
number a = 100
boolean b = false
]
2.3.1.3 代码丑化(简称请不要这么做)
因为换行在 WTCD 中与空格的作用几乎一致,这个定义也可以写成:
declare [ number a = 100 boolean b = false ]
其中大多数空格甚至能被省略:
declare[number a=100boolean b=false]
Just because you can doesn't mean you should. 尽管换行和许多空格是可选的,但是为了可读性,强烈建议不要忽略它们。
2.3.1.4 自动初始化
与此同时,类型为 number
,boolean
,string
的变量的初始化可以被省略。如果省略,它们将会被自动初始化为 0
,false
,和 ""
(空字符串)。
因此,如下两组定义效果是一致的。
declare [
number a = 0
boolean b = false
string c = ""
]
declare [
number a
boolean b
string c
]
2.3.2 变量的赋值
变量几乎可以在所有需要值的场合使用。这里先介绍如何改变变量的值。
给出如下定义:
declare number a
那么在需要赋值的时候就可以写:
a = 100
这个操作就会把 a
的值设置为 100。
当然,等号右侧也可以换成一个表达式:
a = a + 50
这个操作就会让 a
的值自增 50。不过这个操作可以用 +=
操作符代替:
a += 50
具体表达式有哪些会在 2.4 表达式 中介绍。
这里需要注意的是,WTCD 的解释器在运行时会检查赋值给某个变量的值是否和这个变量的类型相同。举个例子,如下代码就会在运行时报错:
declare number a
a = true // WTCDError: Variable "a" can only hold values of type number. Received boolean false.
2.3.3 变量的作用域
WTCD 使用静态作用域(Lexical Scoping)。每一个由大括号包裹的语句组(见 2.4.3)将组成一个新的作用域。
每个定义的变量的作用域从定义位置开始一直延伸到当前作用域结束。最外层定义的变量是全局变量。
举个例子:
declare number outside = 10
{
declare number inside = 20
outside // 10
inside // 20
}
outside // 10
inside // WTCDError: Cannot locate lexical scope for variable "inside".
someVariable // WTCDError: Cannot locate lexical scope for variable "someVariable".
declare number someVariable = 100
内层作用域定义的变量会暂时覆盖(shadow)外层作用域定义的同名变量:
declare number someVariable = 100
{
declare boolean someVariable = false
someVariable // false
}
someVariable // 100
在同一作用域内不能定义同名变量:
declare number hey = 200
declare number hey = 300 // WTCDError: Variable "hey" has already been declared within the same lexical scope.
2.4 表达式
表达式就是由能够表达一个值的式子。简单的表达式由变量,数字,或者操作符组成。而复杂的表达式可以是一个语句组。
2.4.1 常量表达式(Literal Expression)
常量表达式,就是表示常量的表达式。在 WTCD 中,常量表达式有 4 种:数字,布尔值,字符串,null。
数字,布尔值,和 null 都很好理解:
// 数字常量表达式
100
12.7 // 支持小数
.68 // 0. 开头的数字中的 0 可以省略
(-20) // 这个其实不是数字常量表达式,而是一个一元运算符表达式,但是为了方便理解,这里也列出来了
// 布尔值常量表达式
true
false
// null 常量表达式
null // 只能是 null
WTCD 的字符串常量表达式稍微复杂一些。WTCD 允许使用双引号("
),单引号('
),或者反引号(`
)来包裹字符串:
"Hello"
'World'
`Test`
WTCD 的字符串常量表达式支持使用反斜杠(\
)进行 escape:
"\n" // -> 换行
"\t" // -> TAB
"\'" // -> 单引号
"\"" // -> 双引号
"\`" // -> 反引号
"\\" // -> 反斜杠
同时 WTCD 支持跨行字符串常量表达式:
"This is a
poem." // -> "This is a\npoem"
2.4.2 操作符表达式
WTCD 允许使用操作符来操作不同的值。这里先给几个例子:
1 + 1 // -> 2
1 + 2 * 3 // -> 7(乘法优先级更高)
(1 + 2) * 3 // -> 9(括号优先运算)
2 ** 3 // -> 8(指数)
2 ** 3 ** 2 // -> 64(注意,WTCD 的指数是左结合的)
"a" + "b" // -> "ab"(字符串拼接)
4 < 10 // -> true
5 >= 5 // -> true
3 == 3 // -> true
3 == "3" // -> false(无自动类型转换)
3 != 3 // -> false
5 / 2 // -> 2.5(number 可存储小数)
5 ~/ 2 // -> 2(truncate 整除)
-5 ~/ 2 // -> -2(truncate 整除永远都往零靠)
5 % 2 // -> 1(求余)
-10 // -> -10
!false // -> true
true ? 1 : 2 // -> 1(三元/逻辑选择)
false && true // -> false
false || true // -> true
a = 3 // -> a 变成 3(变量赋值)
a += 5 // -> a 加上了3(变量累加)
完整的操作符请参见下表(并不需要全部记住):
元数 | 操作符 | 优先级 | 作用 | 例子 |
---|---|---|---|---|
2 | :: | 20 | 函数调用 | mathMax::[1 2 3] = 3 |
2 | ?:: | 20 | 可选函数调用 | mathMax?::[1 2 3] = 3, null?::[1 2 3] = null |
2 | .: | 20 | 右起偏函数调用 | `[ 0 1 2 3 ] |
2 | :. | 20 | 左起偏函数调用 | `"Wow" |
2 | . | 19 | List 成员访问 | [ 0 1 2 3 ].(2) = 2,[ 0 1 2 3 ].(4) = 报错 |
2 | .? | 19 | 越界即空 List 成员访问 | [ 0 1 2 3 ].(2) = 2,[ 0 1 2 3 ].?(4) = null |
2 | ?. | 19 | 可选 List 成员访问 | null?.(4) = null,[ 0 1 2 3 ]?.(4) = 报错 |
2 | ?.? | 19 | 可选越界即空 List 成员访问 | null?.(4) = null,[ 0 1 2 3 ]?.?(4) = null |
1 | - | 17 | 取负 | -42 = -42 |
1 | ! | 17 | 取逻辑反 | !true = false, !false = true |
2 | ** | 16 | 指数(左结合) | 2 ** 3 = 8, 2 ** 3 ** 2 = 64 |
2 | * | 15 | 数字乘法 | 5 * 10 = 50 |
2 | / | 15 | 数字除法 | 10 / 5 = 2, 5 / 2 = 2.5 |
2 | ~/ | 15 | Truncate 整除 | 5 ~/ 3 = 1, -5 ~/ 3 = -1 |
2 | % | 15 | 求余 | 5 % 2 = 1, -9 % 5 = -4 |
2 | + | 14 | 数字加法或字符串拼接 | 5 + 3 = 8, "a" + "b" = "ab" |
2 | - | 14 | 数字减法 | 5 - 3 = 2, 3 - 5 = -2 |
2 | < | 12 | 判断是否小于 | 3 < 5 = true, 5 < 5 = false, 7 < 5 = false |
2 | <= | 12 | 判断是否小于或等于 | 3 <= 5 = true, 5 <= 5 = true, 7 <= 5 = false |
2 | > | 12 | 判断是否大于 | 3 > 5 = false, 5 > 5 = false, 7 > 5 = true |
2 | >= | 12 | 判断是否大于或等于 | 3 >= 5 = false, 5 >= 5 = true, 7 >= 5 = false |
2 | == | 11 | 判断是否相等 | 5 == 5 = true, 5 == 3 = false, "5" == 5 = false |
2 | != | 11 | 判断是否不相等 | 5 != 5 = false, 5 != 3 = true, "5" != 5 = true |
2 | && | 7 | 逻辑与 | `false |
2 | || | 6 | 逻辑或 | `false |
2 | ?! | 6 | 不可空提供默认值 | 2 ?! 3 = 2, null ?! 3 = 3, null ?! null = 报错 |
2 | ?? | 6 | 提供默认值 | 2 ?? 3 = 2, null ?? 3 = 3, null ?? null = null |
2 | |> | 5 | 管道调用 | `"Hello" |
2 | |:: | 5 | 逆向调用 | `[1 2 3] |
3 | ? : | 4 | 逻辑选择 | true ? 1 : 2 = 1, false ? 1 : 2 = 2 |
2 | = | 3 | 赋值 | a = 10 把 10 赋值给 a,然后返回 10 |
2 | += | 3 | 数字/字符串累加 | a += 10 = a = a + 10 , b += "a" = b = b + "a" |
2 | -= | 3 | 数字累减 | a -= 10 = a = a - 10 |
2 | *= | 3 | 数字累乘 | a *= 10 = a = a * 10 |
2 | /= | 3 | 数字累除 | a /= 10 = a = a / 10 |
2 | ~/= | 3 | 数字累 Truncate 除 | a ~/= 10 = a = a ~/ 10 |
2 | %= | 3 | 数字累求余 | a %= 10 = a = a % 10 |
2.4.3 语句组表达式
语句组表达式允许你在一个需要表达式的地方提供一系列的语句。你需要用大括号({
和 }
)将你的语句包裹起来。
目前,语句可以是:
- 变量定义
- 表达式
yield
/yield =
/return
/return =
其中,变量定义就是定义局部变量。表达式就是计算某个表达式。而 yield
则是指定当前语句组的返回值。
举个例子:
declare [
number a = 1
number b = 2
number c = 3
number squareOfSum
]
squareOfSum = {
// 定义一个局部变量 sum 用来存储 a,b,c 的和
declare number sum = a + b + c
// 将该语句组的返回值设置为 sum * sum
yield sum * sum
// 这句话不会执行,因为 yield 会中断当前语句组的执行
squareOfSum = 66666
}
运行后,squareOfSum
会变成 36。
除此之外 yield
还有一个变种:yield =
。yield =
会设置当前语句组的返回值但是不会中断运行。举个例子:
declare [
boolean changed = false
number a
]
a = {
yield = 10
changed = true
}
上述例子运行后,a
会变成 10,而 changed
会变成 true,因为 yield = 10
并不会停止运行。
需要注意的是最后一个执行的 yield =
会覆盖之前执行的 yield =
。此外 yield
不是一个变量,因此并不能尝试获得它的值。