Scala 词法语法完全指南:字符分类、标识符、字面量与换行规则

发布时间:2026/9/22 11:15:32

Scala 词法语法完全指南:字符分类、标识符、字面量与换行规则 编程语言编译器标准库语言运行时【免费下载链接】scalaScala 2 compiler and standard library. Scala 2 bugs at https://github.com/scala/bug; Scala 3 at https://github.com/scala/scala3项目地址https://gitcode.com/gh_mirrors/sc/scala点击查看免费下载本指南以 Scala 官方语言规范spec/01-lexical-syntax.md第一章「词法语法」为骨架系统讲解 Scala 源码如何从 Unicode 文本被切分为 token包括字符分类体系、三种标识符形态、保留字清单、语句结束的换行判定规则、各类字面量的完整语法以及注释、尾随逗号和 XML 模式的特殊处理。文中同时结合本仓库编译器与标准库实现Scanners.scala、Chars.scala、StringContext.scala给出源码级佐证。读完本文你将能够准确预判任意一段 Scala 代码会被如何切词、为何1.toString是三个 token、为何yield不能直接用作变量名以及多行字符串与插值字符串的边界行为。源文件与字符分类Scala 源码由 Unicode 文本构成。程序文本按照本章描述的方式被切分为 token词法单元。为构造 token字符被划分为以下几类括号内为 Unicode 通用类别类别包含字符Unicode 类别空白字符\u0020空格、\u0009水平制表符、\u000D回车、\u000A换行—字母小写字母Ll、大写字母Lu、标题字母Lt、其他字母Lo、修饰字母Lm、字母数字Nl外加\u0024$与\u005F_多类数字0至9Nd括号( ) [ ] { }—分隔符 . ; ,—运算符字符除上述集合外的全部可打印 ASCII 字符\u0020–\u007E、数学符号Sm、其他符号SoSm、So这份分类在编译器中有直接实现。Chars.scala 中的isIdentifierStart/isIdentifierPart基于Character.isUnicodeIdentifierStart/Part判定字母类字符isOperatorPart显式枚举~ ! # % ^ * - ? : | / \等 ASCII 运算符字符并叠加isSpecialUnicodeMATH_SYMBOL与OTHER_SYMBOLisScalaLetter则对应规范中的字母类。词法分析器 Scanners.scala 正是通过getIdentRest/getOperatorRest与这些谓词协同完成切词见下文。禁止出现的双向控制字符规范规定九个 Unicode 双向显式格式化字符被禁止出现在源码文件中\u202a–\u202e与\u2066–\u2069含端点。这些字符在字符串和字符字面量中可用 Unicode 转义\uXXXX表示。编译器在 Scanners.scala 中对应报错found unicode bidirectional character \uXXXX; in a string or character literal, use a unicode escape insteadChars.isBiDiCharacterChars.scala则精确匹配这九个码位。这一限制是为了防止利用双向文本特性制造「视觉与逻辑不一致」的恶意源码bidi attack。标识符Identifiers标识符的 EBNF 语法如下op :: opchar {opchar} varid :: lower idrest boundvarid :: varid | varid plainid :: upper idrest | varid | 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、p2p、empty_?、dot_product_*、_MAX_LEN_。运算符开头的标识符以一个运算符字符开头后接任意运算符字符序列如、。以上两种统称 plain普通标识符。反引号包裹的标识符任意字符串反引号除外置于反引号内如yield、://。宿主系统可能对合法标识符施加额外限制。规范特别指出虽然/是运算符字符但//与/*会开启注释因此要用作标识符时必须以反引号包裹例如def ://(s: String): URI def */*(d: Double): Double最长匹配规则与多数语言一致切词遵循最长匹配longest match规则。例如字符串big_bobdef被分解为三个标识符big_bob、、defdef虽为保留字但此处指反引号外的裸def不构成标识符的一部分。读者可以从扫描器实现观察到这一点getIdentRest 循环吞入字母、数字与$遇到_转入getIdentOrOperatorRest继续吞并随后的字母数字或运算符片段getOperatorRest 则连续吞入运算符字符直到遇到空白或不可作为运算符的字符为止。变量标识符与常量标识符模式匹配规则进一步区分变量标识符以小写字母或_开头与常量标识符其他情形。这里的「小写字母」不仅包含 a–z还包含 UnicodeLl类别以及具有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注意$字符被保留给编译器合成的标识符用户程序不应定义包含$的标识符。这一约定同样体现在 Chars.scalaisIdentifierStart与isIdentifierPart均允许$参与标识符。保留字Reserved Words以下名称是保留字不属于词法标识符id的成员abstract case catch class def do else extends false final finally for forSome if implicit import lazy macro match new null object override package private protected return sealed super this throw trait try true type val var while with yield _ : - : % : # 此外Unicode 运算符\u21D2⇒ASCII 等价与\u2190←ASCII 等价-同样被保留。访问 Java 保留字的方式当需要引用恰好是 Scala 保留字的 Java 标识符时使用反引号包裹。例如Thread.yield()非法yield是 Scala 保留字但Thread.yield()是合法写法。换行字符Newline Characters与语句结束Scala 是面向行的语言语句可由分号或换行终止。换行的判定规则如下semi :: ; | nl {nl}换行何时被当作 nl token源码文本中的换行只有在同时满足以下三个条件时才被当作特殊 tokennl换行之前紧邻的 token能够终止一条语句换行之后紧邻的 token能够开始一条语句该 token 位于启用换行的区域。能够终止语句的 token字面量、标识符以及下列分隔符与保留字this null true false return type xml-start _ ) ] }能够开始语句的 token除下列分隔符与保留字之外的所有 Scala tokencatch else extends finally forSome match with yield , . ; : - : % : # [ ) ] }casetoken 只有后接class或object时才可开始一条语句。换行启用与禁用的区域启用换行整个 Scala 源文件除嵌套的禁用区域外以及匹配的{与}之间的区间除嵌套的禁用区域外。禁用换行匹配的(与)之间、匹配的[与]之间均除外层嵌套的启用区域case与其匹配的之间以及所有 XML 模式区域。注意XML 与字符串字面量中{...}转义的括号字符不是 token因此不会构成启用换行的区域。连续换行与空行正常情况下两个不同行上的相邻非换行 token 之间只会插入一个nltoken即使中间隔了多行。但如果两个 token 之间隔着至少一个完全空白的行不含任何可打印字符的行则会插入两个nltoken。可选 nl 的位置不终止语句完整的 Scala 语法见 13-syntax-summary.md中某些产生式允许可选的nl但不允许分号这意味着这些位置的换行不会终止表达式或语句条件表达式或 while 循环的条件与下一个表达式之间for 推导式的枚举器与下一个表达式之间类型定义或声明中初始type关键字之后。允许单个换行 token 的位置在{之前若该{是当前语句或表达式的合法延续在 infix 运算符之后若下一行第一个 token 能开始一个表达式在参数列表之前在注解之后。规范给出了多组「有无额外换行导致语义迥异」的对照示例// 合法换行不终止语句 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 { ... }这些示例表明单换行在语法允许位置被吸收而空行产生的双nl则一定会终止语句。这也是 Scala 代码风格中「空行即分隔」的底层机制。字面量Literals字面量涵盖整数、浮点数、字符、布尔值、符号、字符串其语法与 Java 一致Literal :: [-] integerLiteral | [-] floatingPointLiteral | booleanLiteral | characterLiteral | stringLiteral | interpolatedString | symbolLiteral | null整数字面量integerLiteral :: (decimalNumeral | hexNumeral | binaryNumeral) [L | l] decimalNumeral :: digit {digit} hexNumeral :: 0 (x | X) hexDigit {hexDigit} binaryNumeral :: 0 (b | B) binaryDigit {binaryDigit}Int覆盖 $-2^{31}$ 到 $2^{31}-1$Long覆盖 $-2^{63}$ 到 $2^{63}-1$。超出范围的整数字面量是编译错误。整数字面量默认为Int带L/l后缀则为Long小写l因可读性原因被弃用。若字面量的期望类型pt为Byte、Short或Char且数值落在对应范围内则字面量自动转换并采用该类型。范围如下类型范围Byte$-2^7$ 到 $2^7-1$Short$-2^{15}$ 到 $2^{15}-1$Char$0$ 到 $2^{16}-1$数字字面量中可用任意多个下划线分隔数字以增强可读性0 21_000 0x7F -42L 0xFFFF_FFFF浮点字面量floatingPointLiteral :: digit {digit} . digit {digit} [exponentPart] [floatType] | . digit {digit} [exponentPart] [floatType] | digit {digit} exponentPart [floatType] | digit {digit} [exponentPart] floatType exponentPart :: (E | e) [ | -] digit {digit} floatType :: F | f | D | d带F/f后缀的浮点字面量为FloatIEEE 754 32 位单精度否则为DoubleIEEE 754 64 位双精度。若浮点字面量后紧跟一个以字母开头的 token两者之间必须有至少一个空白字符。注意1.toString会解析为三个 token整数1、.、标识符toString而1.不是合法浮点字面量.后缺少必需的数字。0.0 1e30f 3.14159f 1.0e-100 .1布尔字面量booleanLiteral :: true | falsetrue与false均为Boolean类型的成员。字符字面量characterLiteral :: (charNoQuoteOrNewline | escapeSeq) 字符字面量是单引号内的单个字符可以是除单引号定界符、\u000ALF、\u000DCR之外的任意 Unicode 字符或由转义序列表示的任意 Unicode 字符a \u0041 \n \t字符串字面量stringLiteral :: {stringElement} stringElement :: charNoDoubleQuoteOrNewline | escapeSeq字符串字面量是双引号内的字符序列可为除双引号定界符、LF、CR 之外的任意 Unicode 字符或由转义序列表示。字符串内含双引号必须转义为\。字符串字面量的值是一个String实例Hello, world!\n \Hello,\ replied the world.多行字符串字面量stringLiteral :: multiLineChars multiLineChars :: {[] [] charNoDoubleQuote} {}多行字符串以三引号 ... 包裹除结尾外不得出现三个及以上连续引号字符。字符不必可打印换行与控制字符均允许。转义序列不处理但 Unicode 转义仍被处理自 2.13.2 起这一行为被弃用。编译器侧 Scanners.scala 的replaceUnicodeEscapesInTriple会对三引号字符串中的 Unicode 转义发出弃用警告提示 Scala 3 或-Xsource-features:unicode-escapes-raw下将忽略这些转义。the present string spans three lines.结果为the present string spans three lines.标准库的stripMargin定义于scala.collection.StringOps可去除多行字符串的前导空白the present string |spans three |lines..stripMargin求值结果为the present string spans three lines.插值字符串Interpolated StringinterpolatedString :: alphaid {[\] interpolatedStringPart | \\ | \} | alphaid {[] [] char \ ( | $) | escape} {} interpolatedStringPart :: printableChar \ ( | $ | \) | escape escape :: $$ | $ | $ alphaid | $ BlockExpr alphaid :: upper idrest | varid插值字符串由「以字母开头的标识符 紧随其后的字符串字面量」组成两者之间不得有空白或注释。字符串字面量可为普通单引号形式或三引号多行形式。插值字符串内不解释通常的转义字符普通与多行形式皆然\不会关闭普通字符串字面量。$转义有三种形式最通用形式$后跟{与}包裹的表达式${expr}。其中的表达式属于BlockExpr语法类别可含多条语句换行有意义。单独的$不允许出现要得到字面$需写$$要得到字面需写\$。简单形式$后跟一个以字母开头、仅含字母数字与下划线的标识符$id等价于${id}。展开后的表达式正常做类型检查StringContext通常解析为 scala 包中的默认实现也可由用户自定义还可通过内置scala.StringContext的隐式转换添加新的插值器。例如implicit class StringInterpolation(s: StringContext) { def id(args: Any*) ??? }标准库中的 StringContext.scala 定义了内置的s、raw、f三个插值器在 2.13 中均通过宏 fast-track 到scala.tools.reflect.FastStringInterpolator实现其中InvalidUnicodeEscapeExceptionStringContext.scala用于校验字符串中 Unicode 转义的合法性。转义序列Escape Sequences字符与字符串字面量中识别下列字符转义序列字符转义Unicode名称字符\b\u0008退格backspaceBS\t\u0009水平制表符HT\n\u000a换行linefeedLF\f\u000c换页form feedFF\r\u000d回车CR\s\u0020空格\\u0022双引号\\u0027单引号\\\u005c反斜杠\此外字符与字符串字面量还识别\uxxxx形式的 Unicode 转义每个x为十六进制数字。若字符或字符串字面量中的反斜杠不能构成合法转义序列则为编译错误。对应实现见 Scanners.scala 的uEscape与非法转义、八进制转义octal escape literals are unsupported的报错逻辑。符号字面量Symbol LiteralssymbolLiteral :: plainid符号字面量x是表达式scala.Symbol(x)的弃用简写。Symbol伴生对象的apply方法会缓存符号的弱引用从而保证相同符号字面量在引用相等reference equality意义上等价。空白与注释Whitespace and Commentstoken 之间可由空白字符和/或注释分隔。注释有两种形式单行注释以//开始延伸到行尾。多行注释介于/*与*/之间。多行注释可以嵌套但必须正确嵌套因此/* /* */会因注释未闭合而被拒绝。扫描器中与此对应的逻辑位于 getOperatorRest运算符字符/后若紧跟*或/则转入skipComment跳过注释否则/仍作为普通运算符字符参与标识符切分。多行表达式中的尾随逗号Trailing Commas若一个逗号,后忽略空白紧跟换行与右括号)、右方括号]或右花括号}则该逗号被视为「尾随逗号」并被忽略。例如foo( 23, bar, true, )这一特性极大方便了多行参数列表、元组、集合字面量的增删与版本管理。XML 模式XML Mode为允许在源码中直接嵌入 XML 片段词法分析会在下列情形从 Scala 模式切换到 XML 模式遇到且该前是空白、左括号(或左花括号{并且紧随其后是一个能开启 XML 名称的字符( whitespace | ( | { ) (XNameStart | ! | ?) XNameStart :: _ | BaseChar | Ideographic // 同 W3C XML但不含 :扫描器在以下两种情况下切回 Scala 模式由初始开始的 XML 表达式或 XML 模式被成功解析或解析器遇到内嵌的 Scala 表达式或模式强制扫描器回到普通模式直至该 Scala 表达式或模式解析完成。由于代码与 XML 片段可以互相嵌套解析器需要维护一个栈来准确反映 XML 与 Scala 的嵌套层次。在 XML 模式下不构造任何 Scala token注释也被解释为文本。规范示例——一个含两处内嵌 Scala 表达式的 XML 字面量val b book titleThe Scala Language Specification/title version{scalaBook.version}/version authors{scalaBook.authors.mkList(, , , )}/authors /book延伸阅读完整 Scala 语法汇总见 spec/13-syntax-summary.md条件表达式与 while 循环的换行规则见 spec/06-expressions.md类型定义与类型别名type关键字后的换行见 spec/04-basic-declarations-and-definitions.md注解相关语法见 spec/11-annotations.md编译器词法分析实现见 Scanners.scala字符分类工具见 Chars.scala字符串插值器实现见 StringContext.scala赞分享编程语言编译器标准库语言运行时【免费下载链接】scalaScala 2 compiler and standard library. Scala 2 bugs at https://github.com/scala/bug; Scala 3 at https://github.com/scala/scala3项目地址https://gitcode.com/gh_mirrors/sc/scala点击查看免费下载相关推荐Carbon 语言词法单元 Word 完全指南关键字、标识符、类型字面量与 Raw 标识符Carbon 语言词法单元 Word 完全指南关键字、标识符、类型字面量与 Raw 标识符 本文基于 docs/design/lexical_conven编程语言编译器标准库Swift 词法结构Lexical Structure完全解析从标记、标识符到字面量与运算符的底层语法规则Swift 词法结构Lexical Structure完全解析从标记、标识符到字面量与运算符的底层语法规则 本篇技术指南以《The Swift Progr文档教程AssetRipper 实战教程Unity 资源提取与资产导出的最短路径AssetRipper 实战教程Unity 资源提取与资产导出的最短路径 AssetRipper 是一个开源的 GUI 工具专门做 Unity 资源提取 与开发工具逆向工程游戏开发上一篇OpenProject 工作包工作流配置详解基于矩阵的状态迁移权限控制与批量复制下一篇如何在5分钟内用Turbo Boost Switcher控制Mac过热完整温度管理指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/22 11:10:32

akshare 列名报错?TaoToken 这样改 Codex 的 Base URL

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/22 12:10:42

3个案例讲透方式和方法的区别与性能优化

3个案例讲透方式和方法的区别与性能优化 刚把项目从 v2.0 升到 v3.0,发现原本跑得飞快的接口突然变慢,API 文档里那些熟悉的调用方式全变了,连错误码都换了套体系。这种“版本升级后 API…

2026/9/22 12:10:42

DNF背景故事代码化解析:3个技巧搞定性能优化面试

DNF背景故事代码化解析:3个技巧搞定性能优化面试 面试官问:“你懂DNF背景故事里的性能优化吗?”我当场愣住。别笑,这不是段子。去年我面一家大厂,技术二面官拿着DNF的剧情截图问:“这段回忆杀动画加载卡了3秒,你怎么优化?”我脑子里全是阿…

2026/9/22 12:10:42

时间是相对的高频面试题:从零搭建相对时间展示引擎

时间是相对的高频面试题:从零搭建相对时间展示引擎 面试被问“如何优雅展示‘3分钟前’这种相对时间”,90%的候选人卡壳。这不仅是前端细节,更是考察你对时间戳处理、性能优化及边界情况(如跨时区、时差计算)理解的高频面试题。别慌,今天我们从零搭…

2026/9/22 12:05:42

面试总挂?手写实现提交中逻辑,3招搞定并发与状态

面试总挂?手写实现提交中逻辑,3招搞定并发与状态 面试被问“如何保证提交中的幂等性”时,你支支吾吾答不上来,心里是不是在滴血?很多开发者平时只会在业务代码里加个 if (status == 1)…

2026/9/22 10:02:42

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/22 9:07:39

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/22 0:04:49

输电线路在线监测高频面试题拆解 3秒抓住官方文档重点

输电线路在线监测高频面试题拆解 3秒抓住官方文档重点 官方文档几百页翻到头还是懵?面试问到 输电线路在线监测 的数据链路时,脑子一片空白?别慌,这种 高频面试题 我整理了10年,专门治各种“文档太长抓不住重点”的毛病。…

2026/9/22 0:04:49

中介房源管理系统重构避坑:3个关键步骤搞定API变更

中介房源管理系统重构避坑:3个关键步骤搞定API变更 版本升级后 API 全变了,这种痛只有真做过的人懂。 很多团队在接手老旧房产项目时,最崩溃的不是代码烂,而是底层框架升级后,原本熟悉的接口调用方式彻底失效。 这份 保姆级教程…

2026/9/22 0:04:49

3个坑点带你一文搞懂55gg小游戏源码

3个坑点带你一文搞懂55gg小游戏源码 盯着控制台满屏的红色报错,看着那一长串 StackTrace ,是不是脑子瞬间宕机?别急,这种时候最忌讳的就是盲目改代码。很多刚入行的前端同学,面对 55gg 小游戏这类轻量级 H5…

2026/9/20 4:54:47

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/21 18:32:12

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/21 10:29:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码