1. 词法
Scala 程 序 使 用的 字 符集 是 Unicode 的基 本多 文 种 平 面字 符 集 ;目前 不 支 持
Unicode 中增补的字符。本章定义了 Scala 词法的两种模式:Scala 模式与 XML 模式。
如果没有特别说明,以下对 Scala 符号的描述均指 Scala 模式,常量字符„c‟指 ASCII
段\u0000-\u007F。
在 Scala 模式中,十六进制 Unicode 转义字符会被对应的 Unicode 字符替换。
UnicodeEscape ::= \{\\}u{u} hexDigit hexDigit hexDigit hexDigit
hexDigit
::= „0‟|...|„9‟|„A‟|...|„F‟|...|„a‟|...|„f‟|
符号由下面几类字符构成(括号中是 Unicode 通用类别):
1. 空白字符。\u0020|\u0009| \u000D|\u000A
2. 字母,包括小写 (Ll),大写 (Lu),词首字母大写 (Lt),其他 (Lo),数字
(Nl),以及\u0024 ‟$‟ 和 \u005F „_‟,这两个字母归类为大写字母
3. 数字 „0‟|...|„9‟
4. 括号 „(‟ | „)‟ | „[‟ | „]‟ | „{‟ | „}‟。
5. 分隔符 „„‟ | „‟‟ | „”‟ | „.‟ | „;‟ | „,‟。
6. 算符字符。由所有的没有包括在以上分类中的可打印 ASCII 字符\u0020-
\u007F,数学符号(Sm)以及其他符号(So)构成
1.1.
语法:
op
标识符
::= opchar {opchar}
::= lower idrest
::= upper idrest
| varid
| op
varid
plainid
id
idrest
::= plainid
| „\‟‟ stringLit „\‟‟
::= {letter | digit}[„_‟ op]
有三种方法可以构造一个标识符。第一,首字符是字母,后续字符是任意字母和数字。
这种标识符还可后接下划线‟_‟,然后是任意字母和数字。第二,首字符是算符字符,后
续字符是任意算符字符。这两种形式是普通标识符。最后,标识符可以是由反引号‟`‟括
起来的任意字符串(宿主系统可能会对字符串和合法性有些限制)。这种标识符可以由除了
反引号的任意字符构成。
1
换行字符
按惯例,标识符符合最长匹配原则。例如:
Big_bob++=‟def‟
可以分解为三个标识符 big_bob,++=,和 def。变量标识符(varid,小写字母开头)
和常量标识符(没有小写字母开头的限制)的模式匹配规则有所不同。
以下命名是保留字,不能用作词法标识符的语法类 id。
abstract
do
finally
import
object
requires
throw
val
_
:
=
case
else
for
lazy
override
return
trait
var
=>
<-
<:
catch
extends
forSome
match
package
sealed
try
while
<%
>:
#
class
false
if
new
private
super
true
with
@
def
final
implicit
null
protected
this
type
yield
Unicode 算符\u21D2 „⇒‟ 和 \u2190 „←‟以及它们的 ASCII 对应‟=>‟也是保
留字
示例 1.1.1 以下是一些标识符的示例
x
+
__system
Object
_MAX_LEN_
maxIndex
_y
p2p
empty_?
dot_product_*
`yield` αρετη
示例 1.1.2 反引号括起来的字符串是那些 Scala 中是保留字的 Java 中标识符的一
个方法。例如,在 Scala 中 Thread.yield()是非法的,因为 yield 是保留字。但
是可以这样调用:
Thread.`yield`()
1.2.
语法:
换行字符
::= „;‟ | nl{nl}
semi
Scala 是一个基于行的语言。分号和换行均可作为语句的结束。如果换行满足以下三
个条件则会被认为是一个特殊符号‟nl‟:
1. 换行之前的符号是一个语句的结束
2. 换行之后的符号是一个语句的开始
3. 符号处在一个允许多语句的区域中
可以作为语句结束的符号是:常量,标识符,保留字以及以下的分隔符:
this
-
null
)
true
]
false
}
return
type
<xml-start>
2
可以作为语句开始的符号是除了以下分隔符及保留字之外的所有 Scala 符号:
catch
with
]
}
else
yield
extends finally forSome match
,
.
;
:
_
=
=>
<-
requires
<:
<%
>:
#
[
)
符号 case 只有在 class 或者 object 符号之前才可以作为语句开始。
多行语句许可的条件:
1. 整个 Scala 源文件中,除了换行被禁止的嵌套区域
2. 在匹配的{与}之间,除了换行被禁止的嵌套区域
多行语句在以下区域被禁止:
1. 在匹配的(与)之间,除了换行被允许的嵌套区域。
2. 在匹配的[与]之间,除了换行被允许的嵌套区域。
3. 在 case 符号以及与其匹配的=>符号之间,除了换行被允许的嵌套区域。
4. XML 模式下的区域(§1.5)。
注意在 XML 中大括号{..}被转义,字符串并不是符号。因此当换行被允许时不要关
闭区域。
一般地,即使连续的两个非换行符号中有多行,也只会插入一个 nl 符号。然而,如
果两个符号被至少一个空行(行中没有可打印字符)分隔开,那么两个符号中就会插入两个
nl 符号。
Scala 语法(全文见附录 A)允许可选的 nl 符号,但分号不在此列。这样在某些位置
换行并不会结束一个表达式或语句。这些位置如下所列:
以下位置允许多个换行符号(换了分号是不行地):
- 在条件表达式(§6.16) 或 while 循环(§6.17)的条件及下一个表达式间
- For 循环(§6.19)中计数器及下一个表达式间
- 类型定义或声明中,在开始的 type 关键字之后
以下位置允许单个换行:
- 在一个是当前语句或表达式的合法继续的大括号”{”前
- 如果下行的第一个符号是一个表达式的开始(§6.12),本行的中缀算符之后
- 在一个参数子句前(§4.6)
- 在一个标注(§11)之后
示例 1.2.1 以下是跨两行的四个合法语句。两行间的换行符号并未作为语句结束。
if(x > 0)
x=x–1
while(x > 0)
x=x/2
for(x <- 1 to 10)
println(x)
type
IntList = List[Int]
3
示例 1.2.2 以下代码定义了一个匿名类
new Iterator[Int]
{
private var x = 0
def hasNext = true
def next = { x += 1; x }
}
加一个换行后,同样的代码就成了一个对象创建和一个局部代码块
new Iterator[Int]
{
private var x = 0
def hasNext = true
def next = { x += 1; x }
}
示例 1.2.3 以下代码定义了一个表达式:
x < 0 ||
x > 10
加一个换行后就成了两个表达式:
x < 0 ||
x > 10
示例 1.2.4 以下代码定义了一个单一的柯里化的函数:
def func(x: Int)
(y: Int) = x + y
加一个换行后,同样的代码就成了一个抽象函数和一个非法语句
def func(x: Int)
(y: Int) = x + y
示例 1.2.5 以下代码是一个加了标注的定义:
@serializable
protected class Data{...}
加一个换行后,同样的代码就成了一个属性标记和一个单独的语句(实际上是非法的)
@serializable
protected class Data{...}
4
1.3.
字面值
字面值包括整数,浮点数,字符,布尔值,记号,字符串。这些字面值的语法均和
Java 中的字面值一致。
语法:
Literal ::= [„-„] integerLiteral
| [„-„] floatingPointLiteral
| booleanLiteral
| characterLiteral
| stringLiteral
| symbolLiteral
| „null‟
1.3.1. 整型字面值
语法:
integerLiteral ::= (decimalNumeral | hexNumeral | octalNumeral)[„L‟|„l‟]
decimalNumeral ::= „0‟ | nonZeroDigit {digit}
hexNumeral
octalNumeral
digit
nonZeroDigit
octalDigit
::= „0‟„x‟ hexDigit {hexDigit}
::= „0‟ octalDigit {octalDigit}
::= „0‟ | nonZeroDigit
::= „1‟ | ... | „9‟
::= „0‟ | ... | „7‟
整型字面值通常表示 Int 型,或者后面加上 L 或 l 表示 Long 型。Int 的值的范围是
-2 到 231-1 间的整数,包含边界值。Long 的值的范围是-263 到 263-1 间的整数,包含
边界值。整型字面值的值超出以上范围就会导致编译错误。
如果一个字面值在表达式中期望的类型 pt(§6.1)是 Byte, Short 或者 Char 中的
一个,并且整数的值符合该类型的值的范围,那么这个数值就会被转为 pt 类型,这个字
面值的类型也是 pt。数值范围如下所示:
31
Byte
Short
Char
-27 到 27-1
-215 到 215-1
0 到 216-1
示例 1.3.1 以下是一些整型字面值:
0
21
0xFFFFFFFF 0777L
1.3.2. 浮点型字面值
语法:
floatingPointLiteral
::= digit { digit } „.‟ { digit } [ exponentPart ]
[ floatType ]
| „.‟ digit { digit } exponentPart [ floatType ]
|digit{digit}exponentPart [ floatType]
5
| digit { digit } [ exponentPart ] floatType
exponentPart
floatType
::= („E‟ | „e‟)[„+‟ | „-‟]digit{digit}
::= „F‟ | „f‟ | „D‟ | „d‟
如果浮点数字面值的后缀是 F 或者 f,那么这个字面值的类型是 Float,否则就是
Double。Float 类型包括所有 IEEE 754 32 位单精度二进制浮点数值,Double 类型
包括所有 IEEE 754 64 位双精度二进制浮点数值。
如果程序中浮点数字面值后面跟一个字母开头的符号,那么这两者之间应当至少有一
个空白字符。
示例 1.3.2 以下是一些浮点型字面值:
0.0
1e30f
3.14159f
1.0e-100
.1
示例 1.3.3 短语„1.toString‟将被解析为三个符号:„1‟, „.‟和„toString‟。但是
如果在句点后插入一个空格,短语„1. toString‟就会被解析为一个浮点数„1.‟和一个
标识符„toString‟。
1.3.3. 布尔型字面值
语法:
booleanLiteral ::= „true‟ | „false‟
布尔型字面值 true 和 false 是 Boolean 类型的成员
1.3.4. 字符型字面值
语法:
characterLiteral
::= „\‟‟ printableChar „\‟‟
| „\‟‟ charEscapeSeq „\‟‟
字符型字面值就是单引号括起来的单个字符。字符可以是可打印 unicode 字符或者
由一个转义序列(§1.3.6)描述的 unicode 字符。
示例 1.3.4 以下是一些字符型字面值:
„a‟
„\u0041‟
„\n‟
„\t‟
注意„\u000A‟不是一个合法的字符常数,因为在处理字面值前已经完成了 Unicode
转换,而 Unicode 字符\u000A(换行)不是一个可打印字符。可以使用转义序列„\n‟或
八进制转义„\12‟来表示一个换行字符(§1.3.6)。
1.3.5. 字符串字面值
语法:
stringLiteral
stringElement
::= „\”‟ {stringElement} „\”‟
::= printableCharNoDoubleQuote | charEscapeSeq
6
字符串字面值是由双引号括起来的字符序列。字符必须是可打印 unicode 字符或者
转义序列(§1.3.6)。如果一个字符串字面值包括双引号,那么这个双引号必须用转义字
符,比如:\”。字符串字面值的值是类 String 的一个实例。
示例 1.3.5 以下是一些字符串字面值
“Hello,\nWorld!”
“This string contains a \” character.”
多行字符串字面值
语法:
stringLiteral
::= „”””‟ multiLineChars ‟”””‟
multiLineChars ::= {[„”‟][„”‟] charNoDoubleQuote}
多行字符串字面值是由三个双引号括起来的字符序列”””...”””。字符序列是除了三
个双引号之外的任意字符序列。字符不一定必须是可打印的;换行或者其他控制字符也是
可以的。Unicode 转义序列也可以,不过在(§1.3.6)中定义的转义序列不会被解析。
示例 1.3.6 以下是一个多行字符串字面值:
”””the present string
spans three
lines.”””
以上语句会产生如下字符串:
the present string
spans three
lines.
Scala 库里包括一个工具方法 stripMargin,可以用来去掉多行字符串行首的空格。
表达式:
”””the present string
spans three
lines.”””.stripMargin
的值为:
the present string
spans three
lines.
stripMargin 方法定义在类 scala.runtime.RichString。由于有预定义的从
String 到 RichString 的隐式转换,因此这个方法可以应用到所有的字符串。
1.3.6. 转义序列
在字符串或字符字面值中可以有以下转义序列:
\b
\t
\u0008:退格 BS
\u0090:水平制表符 HT
7
\n
\f
\r
\”
\‟
\\
\u000a:换行 LF
\u000c: 格式进纸 FF
\u000d:回车 CR
\u0022:双引号 ”
\u0027:单引号 ‟
\u005c:反斜线 \
0 到 255 间的 Unicode 字符可以用一个八进制转义序列来表示,即反斜线‟\‟后跟
最多三个八进制。
在字符或字符串中,反斜线和后面的字符序列不能构成一个合法的转义序列将会导致
编译错误。
1.3.7. 记号字面值
语法:
symbolLiteral
::= „‟‟ idrest
记号字面值‟x 是表达式 scala.Symbol(“x”)的简写形式。Symbol 是一个 case
类(§5.3.2),定义如下:
package scala
final case class Symbol private (name: String) {
override def toString: String = “‟” + name
}
Symbol 的伴随实例的 apply 方法中缓存了一个到 Symbol 的弱引用,因此同样的记
号字面值是引用相等的。
1.4.
空白与注释
符号可由空白字符或注释分隔开。注释有两种格式:
单行注释是由//开始直到行尾的字符序列
多行注释是在/*和*/之间的字符序列。多行注释可以嵌套,但是必须合理的嵌套。因
此像/* /* */这样的注释是非法的,因为有一个没有结束的注释。
1.5.
XML 模式
为了允许包含 XML 片段字面值。当遇到左尖括号‟<‟时,在以下情况下词法分析就会
从 Scala 模式切换到 XML 模式:‟<‟前必须是空白,左括号或者左大括号,‟<‟后必须跟
一个 XML 命名。
语法:
(whitespace | „(‟ | „{‟ } „<‟ (XNameStart | „!‟ | „?‟)
XNameStart ::= „_‟ | BaseChar | Ideographic (和 W3C XML 一样, 但没有„:‟)
扫描器遇到以下条件之一时将会从 XML 模式切换到 Scala 模式:
8
由‟<‟开始的 XML 表达式或模式已被成功解析。
解析器遇到一个内嵌的 Scala 表达式或模式强制扫描器返回正常模式,直到
Scala 表达式或模式被成功解析。在这种情况下,由于代码和 XML 片段可以嵌套,
解析器将会用一个堆栈来储存嵌套的 XML 和 Scala 表达式。
注意在 XML 模式中不会生成 Scala 的符号,注释会解析为文本。
示例 1.5.1 以下定义了一个值,包括一个 XML 字面值,内嵌了两个 Scala 表达式
val b = <book>
<title>The Scala Language Specification</title>
<version>{scalaBook.version}</version>
<authors>{scalaBook.authors.mkList(””,”, “, ””)}</authors>
</book>
l
l
9