3.2 词法结构
17 分钟阅读
原文链接: https://docs.swift.org/latest/documentation/the-swift-programming-language/lexicalstructure/
3.2 词法结构
了解语法中最底层的组成部分。
Swift 的词法结构描述什么样的字符序列构成语言的合法词法单元。这些合法词法单元构成语言最底层的组成部件,后续章节便用它们来描述语言的其余部分。一个词法单元由标识符、关键字、标点、字面量或运算符构成。
大多数情况下,词法单元是按下述语法约束、从输入文本中取尽可能长的子串,由 Swift 源文件的字符生成出来的。这一行为称为最长匹配或最大吞噬。
空白与注释
空白有两个用途:分隔源文件中的词法单元,以及区分前置、后置和中置运算符(参见运算符),除此之外空白会被忽略。下列字符被视为空白:空格(U+0020)、换行符(U+000A)、回车符(U+000D)、水平制表符(U+0009)、垂直制表符(U+000B)、换页符(U+000C)和空字符(U+0000)。
注释在编译器看来与空白等同。单行注释以 // 开始,一直到换行符(U+000A)或回车符(U+000D)为止。多行注释以 /* 开始,以 */ 结束。多行注释允许嵌套,但注释标记必须配对。
注释可以包含额外的格式与标记,详见 Markup Formatting Reference。
Grammar of whitespace:
whitespace → whitespace-item whitespace?
whitespace-item → line-break
whitespace-item → inline-space
whitespace-item → comment
whitespace-item → multiline-comment
whitespace-item → U+0000, U+000B, or U+000Cline-break → U+000A
line-break → U+000D
line-break → U+000D followed by U+000Ainline-spaces → inline-space inline-spaces?
inline-space → U+0009 or U+0020comment →
//comment-text line-break
multiline-comment →/*multiline-comment-text*/comment-text → comment-text-item comment-text?
comment-text-item → Any Unicode scalar value except U+000A or U+000Dmultiline-comment-text → multiline-comment-text-item multiline-comment-text?
multiline-comment-text-item → multiline-comment
multiline-comment-text-item → comment-text-item
multiline-comment-text-item → Any Unicode scalar value except/*or*/
标识符
标识符以大写或小写的字母 A 到 Z、下划线(_)、基本多文种平面中非组合类的字母数字 Unicode 字符,或者基本多文种平面之外且不属于私用区的字符开头。首字符之后,还允许使用数字和组合类 Unicode 字符。
把以下划线开头的标识符、第一个实参标签以下划线开头的下标,以及第一个实参标签以下划线开头的构造器,都视为 internal,即使它们的声明带有 public 访问级别修饰符。这一约定让框架作者可以标记出 API 中调用方不得与之交互或依赖的部分,即使由于某些限制该声明必须是 public。此外,以两个下划线开头的标识符保留给 Swift 编译器和标准库使用。
要把保留字用作标识符,就在它的前后各加一个反引号(`)。例如 class 不是合法的标识符,但 `class` 是合法的。反引号不算标识符的一部分,因此 `x` 与 x 含义相同。
在没有显式参数名的闭包中,参数被隐式命名为 $0、$1、$2,依此类推。这些名字在闭包作用域内是合法的标识符。
对于带属性包装器投影的属性,编译器会合成以美元符号($)开头的标识符。你的代码可以与此类标识符交互,但不能声明以该前缀开头的标识符。更多内容参见属性特性一章中的 propertyWrapper 一节。
Grammar of an identifier:
identifier → identifier-head identifier-characters?
identifier →`identifier-head identifier-characters?`
identifier → implicit-parameter-name
identifier → property-wrapper-projection
identifier-list → identifier | identifier,identifier-listidentifier-head → Upper- or lowercase letter A through Z
identifier-head →_
identifier-head → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2–U+00B5, or U+00B7–U+00BA
identifier-head → U+00BC–U+00BE, U+00C0–U+00D6, U+00D8–U+00F6, or U+00F8–U+00FF
identifier-head → U+0100–U+02FF, U+0370–U+167F, U+1681–U+180D, or U+180F–U+1DBF
identifier-head → U+1E00–U+1FFF
identifier-head → U+200B–U+200D, U+202A–U+202E, U+203F–U+2040, U+2054, or U+2060–U+206F
identifier-head → U+2070–U+20CF, U+2100–U+218F, U+2460–U+24FF, or U+2776–U+2793
identifier-head → U+2C00–U+2DFF or U+2E80–U+2FFF
identifier-head → U+3004–U+3007, U+3021–U+302F, U+3031–U+303F, or U+3040–U+D7FF
identifier-head → U+F900–U+FD3D, U+FD40–U+FDCF, U+FDF0–U+FE1F, or U+FE30–U+FE44
identifier-head → U+FE47–U+FFFD
identifier-head → U+10000–U+1FFFD, U+20000–U+2FFFD, U+30000–U+3FFFD, or U+40000–U+4FFFD
identifier-head → U+50000–U+5FFFD, U+60000–U+6FFFD, U+70000–U+7FFFD, or U+80000–U+8FFFD
identifier-head → U+90000–U+9FFFD, U+A0000–U+AFFFD, U+B0000–U+BFFFD, or U+C0000–U+CFFFD
identifier-head → U+D0000–U+DFFFD or U+E0000–U+EFFFDidentifier-character → decimal-digit
identifier-character → U+0300–U+036F, U+1DC0–U+1DFF, U+20D0–U+20FF, or U+FE20–U+FE2F
identifier-character → identifier-head
identifier-characters → identifier-character identifier-characters?implicit-parameter-name →
$decimal-digits
property-wrapper-projection →$identifier-characters
关键字与标点
下列关键字是保留的,不能用作标识符,除非像上文标识符中所述用反引号转义。除 inout、var 和 let 之外的关键字,可以在函数声明或函数调用中不加反引号地用作参数名。当某个成员与关键字同名时,引用该成员无需用反引号转义,除非"引用该成员"与"使用该关键字"之间存在歧义——例如 self、Type 和 Protocol 在显式成员表达式中具有特殊含义,因此在该上下文中必须用反引号转义。
声明中使用的关键字:
associatedtype、borrowing、class、consuming、deinit、enum、extension、fileprivate、func、import、init、inout、internal、let、nonisolated、open、operator、precedencegroup、private、protocol、public、rethrows、static、struct、subscript、typealias和var。语句中使用的关键字:
break、case、catch、continue、default、defer、do、else、fallthrough、for、guard、if、in、repeat、return、switch、throw、where和while。表达式和类型中使用的关键字:
Any、as、await、catch、false、is、nil、rethrows、self、Self、super、throw、throws、true和try。模式中使用的关键字:
_。以井号(
#)开头的关键字:#available、#colorLiteral、#else、#elseif、#endif、#fileLiteral、#if、#imageLiteral、#keyPath、#selector、#sourceLocation、#unavailable。
注意: 在 Swift 5.9 之前, 下列关键字是保留的:
#column、#dsohandle、#error、#fileID、#filePath、#file、#function、#line和#warning。 它们现在作为宏实现在 Swift 标准库中:column、dsohandle、error(_:)、fileID、filePath、file、function、line和warning(_:)。
- 在特定上下文中保留的关键字:
associativity、async、convenience、didSet、dynamic、final、get、indirect、infix、lazy、left、mutating、none、nonmutating、optional、override、package、postfix、precedence、prefix、Protocol、required、right、set、some、Type、unowned、weak和willSet。在语法中出现它们的那种上下文之外,它们可以用作标识符。
下列词法单元保留作标点,不能用作自定义运算符:(、)、{、}、[、]、.、,、:、;、=、@、#、&(作为前置运算符)、->、`、? 和 !(作为后置运算符)。
字面量
字面量是某个类型的值(例如数字或字符串)在源代码中的表示。
下面是几个字面量的例子:
| |
字面量本身没有类型。相反,字面量被解析为具有无限精度,由 Swift 的类型推断尝试为它推断类型。例如在声明 let x: Int8 = 42 中,Swift 用显式类型标注(: Int8)推断整数字面量 42 的类型是 Int8。如果没有可用的合适类型信息,Swift 会推断该字面量的类型是下表所列的、Swift 标准库中定义的默认字面量类型之一。为字面量值指定类型标注时,标注的类型必须是可以由该字面量值实例化的类型,也就是说,该类型必须遵循下表列出的 Swift 标准库协议。
| 字面量 | 默认类型 | 协议 |
|---|---|---|
| 整数 | Int | ExpressibleByIntegerLiteral |
| 浮点数 | Double | ExpressibleByFloatLiteral |
| 字符串 | String | ExpressibleByStringLiteral;对于只包含单个 Unicode 标量的字符串字面量,还需 ExpressibleByUnicodeScalarLiteral;对于只包含单个扩展字形簇的字符串字面量,还需 ExpressibleByExtendedGraphemeClusterLiteral |
| 正则表达式 | Regex | 无 |
| 布尔值 | Bool | ExpressibleByBooleanLiteral |
例如在声明 let str = "Hello, world" 中,字符串字面量 "Hello, world" 的默认推断类型是 String。另外,Int8 遵循 ExpressibleByIntegerLiteral 协议,因此可以用在声明 let x: Int8 = 42 中整数字面量 42 的类型标注里。
Grammar of a literal:
literal → numeric-literal | string-literal | regular-expression-literal | boolean-literal | nil-literal
numeric-literal → signed-integer-literal | signed-floating-point-literal
boolean-literal →true|false
nil-literal →nil
整数字面量
整数字面量表示精度未指定的整数值。整数字面量默认用十进制表示,你也可以用前缀指定其他进制:二进制字面量以 0b 开头,八进制字面量以 0o 开头,十六进制字面量以 0x 开头。
十进制字面量包含数字 0 到 9;二进制字面量包含 0 和 1;八进制字面量包含 0 到 7;十六进制字面量包含 0 到 9 以及大写或小写的 A 到 F。
负整数字面量通过在整数字面量前加减号(-)表示,例如 -42。
为了便于阅读,数字之间允许使用下划线(_),但它们会被忽略,因此不影响字面量的值。整数字面量可以带前导零(0),但它们同样会被忽略,不影响字面量的进制或值。
除非另有说明,整数字面量的默认推断类型是 Swift 标准库类型 Int。Swift 标准库还为各种尺寸的有符号和无符号整数定义了类型,详见整数。
Grammar of an integer literal:
signed-integer-literal →
-? integer-literal integer-literal → binary-literal
integer-literal → octal-literal
integer-literal → decimal-literal
integer-literal → hexadecimal-literalbinary-literal →
0bbinary-digit binary-literal-characters?
binary-digit → Digit 0 or 1
binary-literal-character → binary-digit |_
binary-literal-characters → binary-literal-character binary-literal-characters?octal-literal →
0ooctal-digit octal-literal-characters?
octal-digit → Digit 0 through 7
octal-literal-character → octal-digit |_
octal-literal-characters → octal-literal-character octal-literal-characters?decimal-literal → decimal-digit decimal-literal-characters?
decimal-digit → Digit 0 through 9
decimal-digits → decimal-digit decimal-digits?
decimal-literal-character → decimal-digit |_
decimal-literal-characters → decimal-literal-character decimal-literal-characters?hexadecimal-literal →
0xhexadecimal-digit hexadecimal-literal-characters?
hexadecimal-digit → Digit 0 through 9, a through f, or A through F
hexadecimal-literal-character → hexadecimal-digit |_
hexadecimal-literal-characters → hexadecimal-literal-character hexadecimal-literal-characters?
浮点字面量
浮点字面量表示精度未指定的浮点值。
浮点字面量默认用十进制表示(不带前缀),但也可以用十六进制表示(带 0x 前缀)。
十进制浮点字面量由一串十进制数字,后跟小数部分、十进制指数,或者两者都有,构成。小数部分由一个小数点(.)后跟一串十进制数字构成;指数由大写或小写的 e 前缀后跟一串十进制数字构成,表示 e 之前的值要乘以 10 的多少次方。例如 1.25e2 表示 1.25 x 10²,求值为 125.0;类似地,1.25e-2 表示 1.25 x 10⁻²,求值为 0.0125。
十六进制浮点字面量由 0x 前缀、可选十六进制小数部分和十六进制指数构成。十六进制小数部分由小数点后跟一串十六进制数字构成;指数由大写或小写的 p 前缀后跟一串十进制数字构成,表示 p 之前的值要乘以 2 的多少次方。例如 0xFp2 表示 15 x 2²,求值为 60;类似地,0xFp-2 表示 15 x 2⁻²,求值为 3.75。
负浮点字面量通过在浮点字面量前加减号(-)表示,例如 -42.5。
为了便于阅读,数字之间允许使用下划线(_),但它们会被忽略,因此不影响字面量的值。浮点字面量可以带前导零(0),但它们同样会被忽略,不影响字面量的进制或值。
除非另有说明,浮点字面量的默认推断类型是 Swift 标准库类型 Double,表示 64 位浮点数。Swift 标准库还定义了 Float 类型,表示 32 位浮点数。
Grammar of a floating-point literal:
signed-floating-point-literal → >
-? floating-point-literal floating-point-literal → decimal-literal decimal-fraction? decimal-exponent?
floating-point-literal → hexadecimal-literal hexadecimal-fraction? hexadecimal-exponentdecimal-fraction →
.decimal-literal
decimal-exponent → floating-point-e sign? decimal-literalhexadecimal-fraction →
.hexadecimal-digit hexadecimal-literal-characters?
hexadecimal-exponent → floating-point-p sign? decimal-literalfloating-point-e →
e|E
floating-point-p →p|P
sign →+|-
字符串字面量
字符串字面量是由引号括起来的一串字符。单行字符串字面量用双引号括起来,形式如下:
| |
字符串字面量不能包含未转义的双引号(")、未转义的反斜杠(\)、回车符或换行符。
多行字符串字面量用三个双引号括起来,形式如下:
| |
与单行字符串字面量不同,多行字符串字面量可以包含未转义的双引号(")、回车符和换行符,但不能包含三个未转义的相邻双引号。
多行字符串字面量开头的 """ 之后的那个换行不属于字符串;字面量结尾的 """ 之前那个换行同样不属于字符串。若要让多行字符串字面量以换行开头或结尾,就把第一行或最后一行写成空行。
多行字符串字面量可以用空格和制表符的任意组合缩进,这些缩进不计入字符串。结尾的 """ 决定缩进量:字面量中每个非空行都必须以与结尾 """ 之前完全相同的缩进开头,制表符与空格之间不做转换。你可以在该缩进之后再加空格和制表符,这些空格和制表符会出现在字符串中。
多行字符串字面量中的换行会被规范化为使用换行符。即使源文件中混用了回车符和换行符,字符串中的所有换行也会变得一致。
在多行字符串字面量中,在一行末尾写反斜杠(\)会从字符串中省略该换行,反斜杠与该换行之间的任何空白也会被省略。你可以用这种语法在源代码中对多行字符串字面量做硬换行,而不改变所得字符串的值。
单行和多行两种形式的字符串字面量都可以通过下列转义序列包含特殊字符:
- 空字符(
\0) - 反斜杠(
\\) - 水平制表符(
\t) - 换行符(
\n) - 回车符(
\r) - 双引号(
\") - 单引号(
\') - Unicode 标量(
\u{n}),其中 n 是一位到八位的十六进制数
把表达式放在反斜杠(\)之后的圆括号里,就可以把表达式的值插入字符串字面量。插值表达式可以包含字符串字面量,但不能包含未转义的反斜杠、回车符或换行符。
例如,下面这些字符串字面量的值全都相同:
| |
用扩展定界符分隔的字符串,是一串由引号和一组配对的一个或多个井号(#)包围的字符。用扩展定界符分隔的字符串形式如下:
| |
用扩展定界符分隔的字符串中的特殊字符,会作为普通字符而非特殊字符出现在结果字符串中。你可以用扩展定界符创建包含那些通常会产生特殊效果的字符(例如生成字符串插值、开始转义序列或终止字符串)的字符串。
下面的例子展示了产生等价字符串值的字符串字面量与用扩展定界符分隔的字符串:
| |
如果你用多个井号构成扩展定界符,不要在井号之间插入空白:
| |
用扩展定界符创建的多行字符串字面量,其缩进要求与普通多行字符串字面量相同。
字符串字面量的默认推断类型是 String。关于 String 类型的更多内容,参见字符串与字符和 String。
用 + 运算符拼接的字符串字面量会在编译期完成拼接。例如下面例子中 textA 和 textB 的值完全相同——运行时不会执行任何拼接。
| |
Grammar of a string literal:
string-literal → static-string-literal | interpolated-string-literal
string-literal-opening-delimiter → extended-string-literal-delimiter?
"
string-literal-closing-delimiter →"extended-string-literal-delimiter?static-string-literal → string-literal-opening-delimiter quoted-text? string-literal-closing-delimiter
static-string-literal → multiline-string-literal-opening-delimiter multiline-quoted-text? multiline-string-literal-closing-delimitermultiline-string-literal-opening-delimiter → extended-string-literal-delimiter?
"""
multiline-string-literal-closing-delimiter →"""extended-string-literal-delimiter?
extended-string-literal-delimiter →#extended-string-literal-delimiter?quoted-text → quoted-text-item quoted-text?
quoted-text-item → escaped-character
quoted-text-item → Any Unicode scalar value except",\, U+000A, or U+000Dmultiline-quoted-text → multiline-quoted-text-item multiline-quoted-text?
multiline-quoted-text-item → escaped-character
multiline-quoted-text-item → Any Unicode scalar value except\
multiline-quoted-text-item → escaped-newlineinterpolated-string-literal → string-literal-opening-delimiter interpolated-text? string-literal-closing-delimiter
interpolated-string-literal → multiline-string-literal-opening-delimiter multiline-interpolated-text? multiline-string-literal-closing-delimiterinterpolated-text → interpolated-text-item interpolated-text?
interpolated-text-item →\(expression)| quoted-text-itemmultiline-interpolated-text → multiline-interpolated-text-item multiline-interpolated-text?
multiline-interpolated-text-item →\(expression)| multiline-quoted-text-itemescape-sequence →
\extended-string-literal-delimiter
escaped-character → escape-sequence0| escape-sequence\| escape-sequencet| escape-sequencen| escape-sequencer| escape-sequence"| escape-sequence'
escaped-character → escape-sequenceu{unicode-scalar-digits}
unicode-scalar-digits → Between one and eight hexadecimal digitsescaped-newline → escape-sequence inline-spaces? line-break
正则表达式字面量
正则表达式字面量是由斜杠(/)包围的一串字符,形式如下:
| |
正则表达式字面量不能以未转义的制表符或空格开头,也不能包含未转义的斜杠(/)、回车符或换行符。
在正则表达式字面量中,反斜杠被理解为该正则表达式的一部分,而不像字符串字面量中那样只是转义字符:它表示紧跟其后的特殊字符应按字面含义解释,或者紧跟其后的非特殊字符应按特殊方式解释。例如 /\(/ 匹配单个左圆括号,/\d/ 匹配单个数字。
用扩展定界符分隔的正则表达式字面量,是由斜杠(/)和一组配对的一个或多个井号(#)包围的一串字符,其形式如下:
| |
使用扩展定界符的正则表达式字面量可以以未转义的空格或制表符开头,可以包含未转义的斜杠(/),也可以跨多行。对于多行正则表达式字面量,开定界符必须位于行末,闭定界符必须独占一行。在多行正则表达式字面量内部,默认启用扩展正则表达式语法——具体来说就是忽略空白并允许注释。
如果你用多个井号构成扩展定界符,不要在井号之间插入空白:
| |
如果你需要空的正则表达式字面量,就必须使用扩展定界符语法。
Grammar of a regular expression literal:
regular-expression-literal → regular-expression-literal-opening-delimiter regular-expression regular-expression-literal-closing-delimiter
regular-expression → Any regular expressionregular-expression-literal-opening-delimiter → extended-regular-expression-literal-delimiter?
/
regular-expression-literal-closing-delimiter →/extended-regular-expression-literal-delimiter?extended-regular-expression-literal-delimiter →
#extended-regular-expression-literal-delimiter?
运算符
Swift 标准库定义了许多运算符供你使用,其中许多在基本运算符和高级运算符中讨论。本节介绍可以用哪些字符定义自定义运算符。
自定义运算符可以以下列 ASCII 字符之一开头:/、=、-、+、!、*、%、<、>、&、|、^、?、~,也可以以下面语法中定义的 Unicode 字符开头(其中包括来自 Mathematical Operators、Miscellaneous Symbols、Dingbats 等 Unicode 块的字符)。首字符之后,还允许使用组合类 Unicode 字符。
你也可以定义以点(.)开头的自定义运算符,这类运算符可以包含更多的点。例如 .+. 被视为单个运算符。如果运算符不以点开头,就不能在别处包含点。例如 +.+ 被视为 + 运算符后面跟着 .+ 运算符。
虽然你可以定义包含问号(?)的自定义运算符,但它们不能只由单个问号字符构成。此外,虽然运算符可以包含感叹号(!),但后置运算符不能以问号或感叹号开头。
注意:词法单元
=、->、//、/*、*/、., 前置运算符<、&、?, 中置运算符?, 以及后置运算符>、!、?都是保留的。 这些词法单元既不能被重载,也不能用作自定义运算符。
运算符周围的空白用于判断它是用作前置运算符、后置运算符还是中置运算符,其规则如下:
- 如果运算符两侧都有空白,或者两侧都没有空白,它被视为中置运算符。例如
a+++b和a +++ b中的+++运算符被视为中置运算符。 - 如果运算符只有左侧有空白,它被视为前置一元运算符。例如
a +++b中的+++运算符被视为前置一元运算符。 - 如果运算符只有右侧有空白,它被视为后置一元运算符。例如
a+++ b中的+++运算符被视为后置一元运算符。 - 如果运算符左侧没有空白,但紧接着一个点(
.),它被视为后置一元运算符。例如a+++.b中的+++运算符被视为后置一元运算符(应读作a+++ .b而不是a +++ .b)。
在这些规则中,运算符前的字符 (、[、{,运算符后的字符 )、]、},以及字符 ,、;、: 也被视为空白。
如果预定义运算符 ! 或 ? 左侧没有空白,它就被视为后置运算符,无论右侧是否有空白:要把 ? 用作可选链运算符,它左侧必须没有空白;要在三元条件(? :)运算符中使用它,它两侧都必须有空白。
如果中置运算符的某个实参是正则表达式字面量,那么该运算符两侧必须有空白。
在某些构造中,以 < 或 > 开头的运算符可能被拆分成两个或更多词法单元,剩余部分按同样方式处理,还可能继续拆分。因此,在 Dictionary<String, Array<Int>> 这样的构造中,你不需要为了区分结尾的 > 字符而添加空白——在这个例子里,两个结尾的 > 字符不会被当作单个词法单元,从而不会被误读为位左移 >> 运算符。
要了解如何定义新的自定义运算符,参见自定义运算符和运算符声明。要了解如何重载已有运算符,参见运算符方法。
Grammar of operators:
operator → operator-head operator-characters?
operator → dot-operator-head dot-operator-charactersoperator-head →
/|=|-|+|!|*|%|<|>|&|||^|~|?
operator-head → U+00A1–U+00A7
operator-head → U+00A9 or U+00AB
operator-head → U+00AC or U+00AE
operator-head → U+00B0–U+00B1
operator-head → U+00B6, U+00BB, U+00BF, U+00D7, or U+00F7
operator-head → U+2016–U+2017
operator-head → U+2020–U+2027
operator-head → U+2030–U+203E
operator-head → U+2041–U+2053
operator-head → U+2055–U+205E
operator-head → U+2190–U+23FF
operator-head → U+2500–U+2775
operator-head → U+2794–U+2BFF
operator-head → U+2E00–U+2E7F
operator-head → U+3001–U+3003
operator-head → U+3008–U+3020
operator-head → U+3030operator-character → operator-head
operator-character → U+0300–U+036F
operator-character → U+1DC0–U+1DFF
operator-character → U+20D0–U+20FF
operator-character → U+FE00–U+FE0F
operator-character → U+FE20–U+FE2F
operator-character → U+E0100–U+E01EF
operator-characters → operator-character operator-characters?dot-operator-head →
.
dot-operator-character →.| operator-character
dot-operator-characters → dot-operator-character dot-operator-characters?infix-operator → operator
prefix-operator → operator
postfix-operator → operator