词法语法

Scala 源代码由 Unicode 文本组成。

程序文本按本章所述进行词法分析。有关 XML 字面量的特殊支持,请参见最后一节,这些字面量在XML 模式下进行解析。

为了构建标记,字符根据以下类别进行区分(括号中给出 Unicode 通用类别)

  1. 空白字符。\u0020 | \u0009 | \u000D | \u000A
  2. 字母,包括小写字母 (Ll)、大写字母 (Lu)、标题字母 (Lt)、其他字母 (Lo)、修饰字母 (Lm)、字母数字 (Nl) 以及两个字符 \u0024 ‘$’\u005F ‘_’
  3. 数字 ‘0’ | ... | ‘9’
  4. 括号 ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’
  5. 分隔符字符 ‘`’ | ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’
  6. 运算符字符。这些字符包括所有不可打印的 ASCII 字符 (\u0020 - \u007E),这些字符不在上述任何集合中,数学符号 (Sm) 和其他符号 (So)。

可选大括号

可选大括号的原则是,任何可以后跟 { 的关键字也可以后跟一个缩进块,而不需要插入 :。(允许可选的 : 会适得其反,因为它会引入几种执行相同操作的方式。)

词法分析器插入 indentoutdent 标记,这些标记表示缩进代码区域 在某些点

在下面的上下文无关产生式中,我们使用符号 <<< ts >>> 来表示一个标记序列 ts,该序列要么包含在一对大括号 { ts } 中,要么构成一个缩进区域 indent ts outdent。类似地,符号 :<<< ts >>> 表示一个标记序列 ts,该序列要么包含在一对大括号 { ts } 中,要么构成一个缩进区域 indent ts outdent,该区域紧跟在 colon 标记之后。

冒号 标记读作标准冒号 ":",但在上下文无关语法允许的情况下,如果前一个标记是字母数字标识符、反引号标识符或以下标记之一:thissupernew、")" 和 "]",则会生成它而不是它。

colon         ::=  ':'    -- with side conditions explained above
 <<< ts >>>   ::=  ‘{’ ts ‘}’
                |  indent ts outdent
:<<< ts >>>   ::=  [nl] ‘{’ ts ‘}’
                |  colon indent ts outdent

标识符

op            ::=  opchar {opchar}
varid         ::=  lower idrest
boundvarid    ::=  varid
                | ‘`’ varid ‘`’
alphaid       ::=  upper idrest
                |  varid
plainid       ::=  alphaid
                |  op
id            ::=  plainid
                |  ‘`’ { charNoBackQuoteOrNewline | escapeSeq } ‘`’
idrest        ::=  {letter | digit} [‘_’ op]
escapeSeq     ::= UnicodeEscape | charEscapeSeq
UnicodeEscape ::= ‘\’ ‘u’ {‘u’} hexDigit hexDigit hexDigit hexDigit
hexDigit      ::= ‘0’ | ... | ‘9’ | ‘A’ | ... | ‘F’ | ‘a’ | ... | ‘f’

有三种方法可以形成标识符。首先,标识符可以以字母开头,后跟任意字母和数字序列。这之后可以是下划线 ‘_‘ 字符,以及另一个由字母和数字或运算符字符组成的字符串。其次,标识符可以以运算符字符开头,后跟任意运算符字符序列。前面两种形式称为普通标识符。最后,标识符也可以由反引号之间的任意字符串形成(主机系统可能会对哪些字符串是标识符的合法字符串施加一些限制)。然后,标识符由除反引号本身之外的所有字符组成。

像往常一样,最长匹配规则适用。例如,字符串

big_bob++=`def`

分解为三个标识符 big_bob++=def

模式匹配规则进一步区分变量标识符(以小写字母或 _ 开头)和常量标识符(不以小写字母或 _ 开头)。

为此,小写字母不仅包括 a-z,还包括 Unicode 类别 Ll(小写字母)中的所有字符,以及具有贡献属性 Other_Lowercase 的所有字母,除了类别 Nl(字母数字)中的字符,这些字符永远不会被视为小写字母。

以下是变量标识符的示例

    x         maxIndex   p2p   empty_?
    `yield`   αρετη      _y    dot_product_*
    __system  _MAX_LEN_
    ªpple     ʰelper

以下是常量标识符的一些示例

    +    Object  $reserved  Džul    ǂnûm
    ⅰ_ⅲ  Ⅰ_Ⅲ     ↁelerious  ǃqhàà  ʹthatsaletter

‘$’ 字符保留用于编译器生成的标识符。用户程序不应定义包含 ‘$’ 字符的标识符。

常规关键字

以下名称是保留字,而不是词法标识符的语法类别 id 的成员。

abstract  case      catch     class     def       do        else
enum      export    extends   false     final     finally   for
given     if        implicit  import    lazy      match     new
null      object    override  package   private   protected return
sealed    super     then      throw     trait     true      try
type      val       var       while     with      yield
:         =         <-        =>        <:        >:        #
@         =>>       ?=>

软关键字

此外,以下软关键字仅在某些情况下保留。

as      derives      end      extension   infix   inline   opaque
open    transparent  using
|       *            +        -

软修饰符是标识符 infixinlineopaqueopentransparent 之一。

软关键字是软修饰符,或 asderivesendextensionusing|+-* 之一。

如果软修饰符后面跟着硬修饰符或以定义开头的关键字组合(defvalvartypegivenclasstraitobjectenumcase classcase object),则将其视为定义的实际修饰符。这两个词之间可能有一系列换行符和/或其他软修饰符。

否则,软关键字在以下情况下被视为实际关键字

在其他所有地方,软关键字被视为普通标识符。

当需要访问在 Scala 中是保留字的 Java 标识符时,请使用反引号括起来的字符串。例如,语句Thread.yield()是非法的,因为yield是 Scala 中的保留字。但是,这里有一个解决方法:Thread.`yield`()

换行符

semi ::= ‘;’ |  nl {nl}

Scala 是一种面向行的语言,其中语句可以用分号或换行符终止。如果满足以下三个条件,Scala 源代码中的换行符将被视为特殊标记“nl”

  1. 紧接在换行符之前的标记可以终止语句。
  2. 紧接在换行符之后的标记可以开始语句,并且不是前导中缀运算符
  3. 该标记出现在允许换行的区域。

可以终止语句的标记包括:字面量、标识符以及以下分隔符和保留字

this    null    true    false    return    type    given    <xml-start>
_       )       ]       }        outdent

可以开始语句的标记是所有 Scala 标记,除了以下分隔符和保留字

catch    do      else    extends    finally    forSome    macro
match    then    with    yield
,    .    ;    :    =    =>    <-    <:    <%    >:    #    =>>    ?=>
)    ]    }    outdent

前置中缀运算符是指诸如 +approx_== 之类的符号标识符,或是在反引号中的标识符,它

此外,如果运算符出现在其自身行上,则下一行必须至少具有与运算符相同的缩进宽度。

换行符在以下情况下启用:

  1. 整个 Scala 源文件,除了换行符被禁用的嵌套区域,以及
  2. 匹配的 {} 大括号标记之间的间隔,除了换行符被禁用的嵌套区域。

换行符在以下情况下禁用:

  1. 匹配的 () 圆括号标记之间的间隔,除了换行符被启用的嵌套区域,以及
  2. 匹配的 [] 方括号标记之间的间隔,除了换行符被启用的嵌套区域。
  3. case 标记与其匹配的 => 标记之间的间隔,除了换行符被启用的嵌套区域。
  4. XML 模式 中分析的任何区域。

请注意,{...} 转义符在 XML 和字符串字面量中的大括号字符不是标记,因此不会包含换行符启用的区域。

通常,在位于不同行上的两个连续的非换行符标记之间只插入一个 nl 标记,即使这两个标记之间有多行。但是,如果两个标记之间至少有一行完全空白(即一行不包含任何可打印字符),则会插入两个 nl 标记。

Scala 语法(完整内容 在此)包含可选 nl 标记(但不是分号)被接受的产生式。这会导致在这些位置中的换行符不会终止表达式或语句。这些位置可以总结如下

在以下位置接受多个换行符标记(请注意,在所有这些情况下,分号代替换行符都是非法的)

单个换行符在以下情况下被接受

两行之间的换行符不被视为语句分隔符。

if (x > 0)
  x = x - 1

while (x > 0)
  x = x / 2

for (x <- 1 to 10)
  println(x)

type
  IntList = List[Int]
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 }
}
  x < 0 ||
  x > 10

如果添加一个额外的换行符,相同的代码将被解释为两个表达式

  x < 0 ||

  x > 10
def func(x: Int)
        (y: Int) = x + y

如果添加一个额外的换行符,相同的代码将被解释为一个抽象方法定义和一个语法上非法的语句

def func(x: Int)

        (y: Int) = x + y
@serializable
protected class Data { ... }

如果添加一个额外的换行符,相同的代码将被解释为一个属性和一个单独的语句(语法上是非法的)。

@serializable

protected class Data { ... }

字面量

存在用于整数、浮点数、字符、布尔值和字符串的字面量。这些字面量的语法在每种情况下都与 Java 中相同。

Literal  ::=  [‘-’] integerLiteral
           |  [‘-’] floatingPointLiteral
           |  booleanLiteral
           |  characterLiteral
           |  stringLiteral
           |  interpolatedString
           |  ‘null’

整数字面量

integerLiteral   ::=  (decimalNumeral | hexNumeral | binaryNumeral) [‘L’ | ‘l’]
decimalNumeral   ::=  ‘0’ | digit [{digit | ‘_’} digit]
hexNumeral       ::=  ‘0’ (‘x’ | ‘X’) hexDigit [{hexDigit | ‘_’} hexDigit]
binaryNumeral    ::=  ‘0’ (‘b’ | ‘B’) binaryDigit [{binaryDigit | ‘_’} binaryDigit]

类型为 Int 的值是所有介于 $-2^{31}$ 和 $2^{31}-1$(含)之间的整数。类型为 Long 的值是所有介于 $-2^{63}$ 和 $2^{63}-1$(含)之间的整数。如果整数字面量表示的数字超出这些范围,则会发生编译时错误。

整数字面量通常为 Int 类型,或者在后面跟着 Ll 后缀时为 Long 类型。(出于可读性的原因,小写 l 已被弃用。)

但是,如果表达式中字面量的预期类型 ptByteShortChar,并且整数在该类型定义的数值范围内,则该数字将转换为 pt 类型,并且字面量的类型为 pt。这些类型给出的数值范围为

Byte ´-2^7´ 到 ´2^7-1´
Short ´-2^{15}´ 到 ´2^{15}-1´
Char ´0´ 到 ´2^{16}-1´

数值字面量的数字可以由任意数量的下划线分隔,以提高可读性。

0           21_000      0x7F        -42L        0xFFFF_FFFF        0b0100_0010

浮点数字面量

floatingPointLiteral  ::=  [decimalNumeral] ‘.’ digit [{digit | ‘_’} digit] [exponentPart] [floatType]
                        |  decimalNumeral exponentPart [floatType]
                        |  decimalNumeral floatType
exponentPart          ::=  (‘E’ | ‘e’) [‘+’ | ‘-’] digit [{digit | ‘_’} digit]

浮点数字面量在后面跟着浮点类型后缀 Ff 时为 Float 类型,否则为 Double 类型。Float 类型包含所有 IEEE 754 32 位单精度二进制浮点值,而 Double 类型包含所有 IEEE 754 64 位双精度二进制浮点值。

如果程序中的浮点数字面量后面跟着以字母开头的标记,则这两个标记之间必须至少有一个空格字符。

0.0        1e30f      3.14159f      1.0e-100      .1

短语 1.toString 解析为三个不同的标记:整数字面量 1、一个 . 和标识符 toString

1. 不是有效的浮点数字面量,因为缺少 . 后面的必需数字。

布尔字面量

booleanLiteral  ::=  ‘true’ | ‘false’

布尔字面量 truefalseBoolean 类型的成员。

字符字面量

characterLiteral  ::=  ‘'’ (charNoQuoteOrNewline | escapeSeq) ‘'’

字符字面量是用引号括起来的单个字符。该字符可以是任何 Unicode 字符,除了单引号分隔符或 \u000A (LF) 或 \u000D (CR);或任何由 转义序列 表示的 Unicode 字符。

'a'    '\u0041'    '\n'    '\t'

字符串字面量

stringLiteral  ::=  ‘"’ {stringElement} ‘"’
stringElement  ::=  charNoDoubleQuoteOrNewline | escapeSeq

字符串字面量是用双引号括起来的字符序列。这些字符可以是任何 Unicode 字符,除了双引号分隔符或 \u000A (LF) 或 \u000D (CR);或任何由 转义序列 表示的 Unicode 字符。

如果字符串字面量包含双引号字符,则必须使用 "\"" 对其进行转义。

字符串字面量的值是 String 类的实例。

"Hello, world!\n"
"\"Hello,\" replied the world."

多行字符串字面量

stringLiteral   ::=  ‘"""’ multiLineChars ‘"""’
multiLineChars  ::=  {[‘"’] [‘"’] charNoDoubleQuote} {‘"’}

多行字符串字面量是用三个引号 """ ... """ 括起来的字符序列。字符序列是任意的,除了它可能只在最后包含三个或更多个连续的引号字符。字符不必是可打印的;换行符或其他控制字符也是允许的。 转义序列 不被处理,除了 Unicode 转义(这在 2.13.2 之后已弃用)。

  """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.collection.StringOps 中定义。

插值字符串

interpolatedString     ::= alphaid ‘"’ {[‘\’] interpolatedStringPart | ‘\\’ | ‘\"’} ‘"’
                         | alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’
interpolatedStringPart ::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape
escape                 ::= ‘$$’
                         | ‘$"’
                         | ‘$’ alphaid
                         | ‘$’ BlockExpr
alphaid                ::= upper idrest
                         |  varid

插值字符串由一个以字母开头的标识符紧跟着一个字符串字面量组成。在引导标识符和字符串的开引号"之间不能有空格字符或注释。插值字符串中的字符串字面量可以是标准的(单引号)或多行的(三引号)。

在插值字符串中,无论字符串字面量是正常的(用单引号括起来)还是多行的(用三引号括起来),都不会解释任何通常的转义字符。请注意,序列\"不会关闭正常的字符串字面量(用单引号括起来)。

美元符号转义有三种形式。最通用的形式是用${}括起一个表达式,即${expr}。在引导$字符后面的花括号中包含的表达式属于语法类别BlockExpr。因此,它可以包含多个语句,并且换行符是有意义的。在插值字符串中不允许单独使用单个‘$’符号。单个‘$’符号仍然可以通过将‘$’字符加倍来获得:‘$$’。单个‘"’符号可以通过序列‘\$"’获得。

更简单的形式由一个‘$’符号后跟一个以字母开头的标识符组成,该标识符仅后跟字母、数字和下划线字符,例如$id。更简单的形式通过在标识符周围加上花括号来扩展,例如$id等效于${id}。在下文中,除非我们明确说明,否则我们假设此扩展已经执行。

扩展后的表达式将按正常方式进行类型检查。通常,StringContext将解析为scala包中的默认实现,但它也可以是用户定义的。请注意,还可以通过对内置的scala.StringContext进行隐式转换来添加新的插值器。

可以编写一个扩展

implicit class StringInterpolation(s: StringContext) {
  def id(args: Any*) = ???
}

转义序列

在字符和字符串字面量中识别以下字符转义序列。

charEscapeSeq unicode name char
‘\‘ ‘b‘ \u0008 退格键 BS
‘\‘ ‘t‘ \u0009 水平制表符 HT
‘\‘ ‘n‘ \u000a 换行符 LF
‘\‘ ‘f‘ \u000c 换页符 FF
‘\‘ ‘r‘ \u000d 回车符 CR
‘\‘ ‘"‘ \u0022 双引号 "
‘\‘ ‘'‘ \u0027 单引号 '
‘\‘ ‘\‘ \u005c 反斜杠 \

此外,字符和字符串文字中识别形式为 \uxxxx 的 Unicode 转义序列,其中每个 x 都是十六进制数字。

如果字符或字符串文字中的反斜杠字符没有开始有效的转义序列,则会发生编译时错误。

空白和注释

标记可以用空白字符和/或注释分隔。注释有两种形式

单行注释是一系列字符,以 // 开头,一直延伸到行尾。

多行注释是一系列字符,位于 /**/ 之间。多行注释可以嵌套,但必须正确嵌套。因此,类似 /* /* */ 的注释将被拒绝,因为注释未终止。

多行表达式中的尾随逗号

如果逗号 (,) 后面紧跟着(忽略空白)换行符和右括号 ())、右方括号 (]) 或右大括号 (}),则该逗号将被视为“尾随逗号”并被忽略。例如

foo(
  23,
  "bar",
  true,
)

XML 模式

为了允许直接包含 XML 片段,当在以下情况下遇到左尖括号“<”时,词法分析器会从 Scala 模式切换到 XML 模式:该“<”必须在空白、左括号或左大括号之前,并且紧跟着一个开始 XML 名称的字符。

 ( whitespace | ‘(’ | ‘{’ ) ‘<’ (XNameStart | ‘!’ | ‘?’)

  XNameStart ::= ‘_’ | BaseChar | Ideographic // as in W3C XML, but without ‘:’

如果以下任一情况发生,扫描器会从 XML 模式切换回 Scala 模式:

请注意,在 XML 模式下不会构造任何 Scala 标记,并且注释被解释为文本。

以下值定义使用包含两个嵌入式 Scala 表达式的 XML 文字

val b = <book>
          <title>The Scala Language Specification</title>
          <version>{scalaBook.version}</version>
          <authors>{scalaBook.authors.mkList("", ", ", "")}</authors>
        </book>