2026/9/25 3:31:53

用 ANTLR v4 解析 Scala 3:grammars-v4 中 Scala3 语法的设计、覆盖率与已知限制

用 ANTLR v4 解析 Scala 3:grammars-v4 中 Scala3 语法的设计、覆盖率与已知限制 编程语言编译器开发工具【免费下载链接】grammars-v4Grammars written for ANTLR v4; expectation that the grammars are free of actions.项目地址https://gitcode.com/gh_mirrors/gr/grammars-v4点击查看免费下载本文面向需要为 Scala 3 构建词法/语法分析工具的开发者完整介绍 grammars-v4 仓库scala/scala3/目录下这套无 actionfree of actions的 Scala 3 ANTLR v4 语法包括它的 EBNF 来源与对齐策略、--3.0-migration兼容选项、对 Scala 3 显著缩进indentation-sensitive语法的 INDENT/DEDENT 注入原理、基于 Trash Toolkittrcover的规则覆盖率98.3%以及 13 个不可达调用点的成因并逐条给出文档承认的已知语法限制。读完本文你将能够理解这套语法的内部设计取舍知道如何用它做覆盖率分析、如何开启 Scala 2 兼容模式以及哪些输入它会有意地过宽接受。一、设计基线EBNF 来源与 Dotty 对齐1.1 为什么选用 docs 版 EBNF该语法文件的直接依据是 Scala 3 官方文档站docs.scala-lang.org/scala3/reference/syntax.html的语法摘要。作者在 readme.md 中明确记录了选择的理由语言规范 3.4 版本的13-syntax-summary页面存在明显缺漏例如缺少Import相关规则因此放弃了规范版改用 docs 版本作为蓝本。1.2 不盲从 EBNF对齐 Dotty 编译器更重要的是文档指出 docs 版 EBNF 在换行newlines、分号semicolons和语句切分statements上存在若干问题。因此实现的指导原则是尽量镜像 Dotty 编译器Scala 3 官方编译器的实际行为而非盲从人工誊录的 EBNF。这一点在源码中有直接印证Scala3LexerBase.cs 与 Scala3LexerBase.java 的注释明确写着Modelled on Dotty (Scala 3 compiler) Scanners.scala handleNewLine logic其换行处理逻辑直接移植自 Dotty 扫描器的handleNewLine两组 Base 类中的 token 集合谓词CanEndStat/CanStartStat/IsStatContinuation/CanStartIndent也在注释中标注为mirroring Dotty Tokens.scala语法规则中多处保留了移除某替代以避免与另一规则歧义的设计注释例如 Scala3Parser.g4 中block规则明确记录了曾删除| USCORE ...与| id ...两个替代的理由——它们已被blockResult的funParams完全包含会制造真正歧义并触发深层 ALL(*) 前瞻。从源码结构看这套语法是一个以官方 EBNF 为骨架、以编译器实现为行为基准的参考实现而非对规范文本的机械转写。二、仓库中的模块构成2.1 语法文件与目标语言scala/scala3/目录包含文件作用Scala3Lexer.g4词法规则约 585 行含关键字、字面量、运算符、标识符与 Unicode 分类 fragmentScala3Parser.g4语法规则约 948 行覆盖 compilationUnit 到各类定义与表达式CSharp/Scala3LexerBase.csC# 词法基类INDENT/DEDENT 注入、换行判定CSharp/Scala3ParserBase.csC# 语法基类migration30()谓词Java/Scala3LexerBase.javaJava 词法基类与 C# 版逻辑一致Java/Scala3ParserBase.javaJava 语法基类pom.xmlMaven 构建ANTLR4 插件生成 自动测试插件desc.xml仓库级描述目标语言与测试输入desc.xml 声明了该语法的目标语言为CSharp;Java并配置了两组测试输入examples/*.scala与examples/lila/app/*.scala。2.2 Maven 构建与自动测试pom.xml 中的配置给出了现成的构建/验证方式antlr4-maven-plugin从Scala3Lexer.g4与Scala3Parser.g4生成解析器并开启visitor与listener两种访问模式antlr4test-maven-plugin以compilationUnit为入口规则grammarName为Scala3exampleFiles指向examples/测试文件扩展名为.scala。也就是说在仓库根目录执行 Maven 即可同时完成代码生成与示例驱动的回归测试无需额外编写测试代码。2.3 示例语料examples/下除 41 个按特性编号的示例如 05_enum.scala、19_inline.scala、41_indentation_syntax.scala之外还包含两类真实世界语料examples/lila/从目录结构看这是 lilalichess 开源国际象棋平台的 Scala 源码树包含app/controllers/、app/views/等大量真实业务代码如 LilaController.scala用于验证语法在大规模真实代码上的健壮性2246.scala2一个以.scala2为后缀的 Apache Spark 源码文件头部为 Apache License包名org.apache.spark.storage.memory专门用于检验--3.0-migration模式对 Scala 2 风格代码的兼容能力。desc.xml的测试通配符examples/*.scala不含.scala2文件说明该文件更多是供手动以迁移模式验证用的对照语料。三、命令行选项--3.0-migration这是本语法唯一对外开放的命令行选项用于在语法层面模拟 Scala 3 编译器在-source:3.0-migration下接受的 Scala 2 兼容语法。OptionDescription--3.0-migration启用 Scala 3 编译器在-source:3.0-migration下接受的 Scala 2 兼容语法。当前启用两类构造._通配导入选择器如import scala.jdk.CollectionConverters._与_通配类型实参如Seq[_]。未开启该标志时上述构造会被拒绝Scala 3 的等价写法分别是.*与?3.1 源码级的标志传递该选项在两个语言实现中通过 Base 类读取C#Scala3LexerBase.cs 用静态只读属性Migration30扫描Environment.GetCommandLineArgs()只要出现忽略大小写匹配的--3.0-migration即置位JavaScala3LexerBase.java 通过读取系统属性sun.java.command并切分参数来判断两者各自配套的Scala3ParserBaseC# 版、Java 版都暴露migration30()方法供 grammar 内的语义谓词semantic predicate调用。3.2 谓词在语法中的两个挂载点在 Scala3Parser.g4 中{migration30()}?守卫出现在两处wildCardSelector| {migration30()}? USCORE—— 允许_作为 Scala 2 风格的导入通配符类型参数位置L207| {migration30()}? USCORE typeBounds?—— 允许_作为通配类型实参。这解释了文档表格中的行为不加该标志时import foo.bar._与Seq[_]都会被拒绝Scala 3 原生写法分别为import foo.bar.*由wildCardSelector : Op中代表*的分支覆盖与Seq[?]。四、缩进敏感语法的 INDENT/DEDENT 注入原理Scala 3 支持显著缩进significant indentation即用缩进而非花括号界定代码块。ANTLR v4 本身不识别缩进因此这套语法把最复杂的部分放进了词法基类。4.1 词法侧的准备在 Scala3Lexer.g4 中NEWLINE被定义为真实 tokenL238-L240而不是默认的skip或 hidden这样基类才能看到换行并测量下一行缩进WS被发送到隐藏通道L244-L246供基类读取行首空白、计算缩进长度同时不干扰解析语法头部声明了合成 tokenINDENT, DEDENTL16它们没有对应词法规则完全由Scala3LexerBase注入。解析器侧则由end_of_stat : NEWLINE | SEMIScala3Parser.g4 L31统一处理语句分隔——无论分隔符是保留在默认通道的 NEWLINE 还是真实分号。4.2 Region 模型Scala3LexerBase.csJava 版 Scala3LexerBase.java 逻辑相同用栈维护四种区域Region语义换行处理TopLevel最外层作用域缩进不变且满足语句边界条件时NEWLINE 留在默认通道作为分隔符Indented缩进块内INDENT … DEDENT 之间同上按缩进增减决定 INDENT/DEDENTInBraces{…}内NEWLINE 在语句边界暴露为分隔符不产生 INDENT/DEDENTInParens(…)或[…]内换行一律抑制隐式行连接NEWLINE 的处理完全复刻 Dotty 语义缩进增加且前序 token 允许开启块时抑制 NEWLINE 并注入 INDENT缩进不变时按CanEndStat(上一个非隐藏 token) CanStartStat(下一行首 token) !IsStatContinuation(下一行首 token)判断是否保留 NEWLINE缩进减少时抑制 NEWLINE、逐个弹出 DEDENT若外层上下文仍需要分隔符则再补发一个 NEWLINE 副本。4.3 四个 token 集合谓词这四个静态谓词直接镜像 DottyTokens.scala中的同名集合是NEWLINE 该不该出现的判定核心CanEndStat上一个 token 能否结束一条语句。包含各类字面量、标识符Id/Varid/BacktickId、运算符Op、USCORE/THIS/SUPER/RETURN、TYPE/GIVEN、右括号类 token、DEDENT/NEWLINE以及 end-marker 关键字IF/WHILE/FOR/MATCH/TRY/VAL/NEW/EXTENSION因为end if、end while等语句以这些关键字 token 结尾CanStartStat下一行首 token 能否开启新语句。在标识符/字面量/左括号基础上还包括NEW/THROW、各种控制流关键字、修饰符关键字ABSTRACT/FINAL/PRIVATE/PROTECTED/OVERRIDE/SEALED、定义关键字CLASS/TRAIT/OBJECT/ENUM/DEF/VAL/VAR/TYPE/GIVEN/IMPORT/EXPORT/PACKAGE/INLINE/LAZY/IMPLICIT/EXTENSION以及上下文关键字OPEN/INFIX/TRANSPARENT/OPAQUE/AS/DERIVES/USING——后者可作普通标识符使用因此也能开启语句IsStatContinuation下一行首 token 属于语句延续集合THEN/ELSE/DO/CATCH/FINALLY/YIELD/MATCH即使CanEndStat成立也抑制 NEWLINE——例如if cond then\n expr中then后的换行不应被当作语句分隔CanStartIndent前一个 token 允许开启新的缩进块THEN/ELSE/DO/CATCH/FINALLY/YIELD/MATCH/COLON/WITH/ASSIGN/ARROW/CTXARROW/LARROW/WHILE/TRY/FOR/IF/THROW/RETURN。4.4 若干工程化的边界处理Base 类里还处理了若干容易出错的边界情形值得注意缩进计算空格每 1 列、制表符对齐到 8 列(length / 8 1) * 8、\f归零——即 tab 按制表位而非固定宽度计算.续行下一行以DOT开头时视为方法链续行抑制 NEWLINE 与 INDENT但仍会按缩进变化发出 DEDENTRPAREN开启缩进extension (params)这类以)结尾、后接缩进方法体的构造允许RPAREN触发 INDENT但限定在外层是Indented/TopLevel且下一行不以extends/with开头时后者是类模板续行而非新块COMMA/RPAREN时的 DEDENT 排空同一行内f: u expr,这类冒号实参在括号内开块却未遇到换行的场景会在逗号/右括号之前主动弹出Indented区域并补发 DEDENT保证括号闭合法则正常空行与注释行整行空白或仅注释的行全部抑制不影响缩进判定EOF文件结束时为所有未闭合的Indented区域补发 DEDENT再输出 EOF。这些细节使得for推导式、match表达式、given ... with等既可用花括号又可用缩进的构造在两种书写方式下都能被正确处理。五、用 trcover 度量规则覆盖率5.1 覆盖率的含义文档说明这套语法使用 Trash Toolkit 的trcover工具做覆盖率验证它会对 ANTLR4 语法做插桩统计examples/中的示例输入实际走到了哪些规则调用点rule call sites即一条语法规则引用另一条规则的每个位置。覆盖数字就是解析过程中被触达的调用点数量。5.2 重新生成覆盖报告在向examples/添加或修改示例后可执行cd Generated-CSharp dotnet trash cover ../examples/*.scala说明Generated-CSharp是 ANTLR 插件生成 C# 解析器后的输出目录该命令会生成cover.html——一份对 grammar 做了高亮标注的副本被触达的调用点有高亮未被触达的调用点则没有高亮。5.3 当前覆盖数据750 of 763 rule call sites covered98.3%。13 个未覆盖调用点分布在 8 条 grammar 替代行上同一替代行上的多个规则引用各计一个调用点因此 8 行对应 13 个点。六、13 个不可达调用点的成因文档逐一给出了这 13 个调用点当前语法与解析器下永久不可达的结构性原因。下面按当前仓库 Scala3Parser.g4 的行号整理原文档行号为评估时快照与当前文件有少量偏移Grammar location当前行号Reason unreachablefunParamClause/typedFunParam约 L173-L1854 个调用点simpleType_: LPAREN nameAndType RPAREN会先吞掉(x: Int)使得funTypeArgs中根本轮不到funParamClauseANTLR 总是优先走infixType替代。对应规则见 funTypeArgs、funParamClauseINLINE infixExpr matchClauseL3472 个调用点postfixExpr ascription?L348排在前面先把inline当作普通标识符消费剩下的x match { … }再被解析成独立语句LPAREN namedExprInParens … RPAREN/namedExprInParensL395、L426-L4283 个调用点LPAREN exprsInParens RPARENL394在simpleExpr中位置更靠前且总是先赢命名实参f(x 1)会被exprsInParens经expr1: id ASSIGN expr吸收变长实参LPAREN … postfixExpr Op RPARENL4342 个调用点LPAREN exprsInParens RPAREN先匹配args*被当作exprsInParens内部的 postfix 表达式解析defSig (COLON type_)?抽象声明约 L7622 个调用点该替代确实会被抽象方法声明执行但覆盖率工具无法独立追踪它多个以defSig (COLON type_)?开头的defDef替代共享同一 ATN 前缀命中被归到第一个替代名下。defDef定义见 L757-L763一个值得注意的细节前四类不可达意味着这些调用点虽然在语法中存在但被更早出现的替代遮蔽属于 ANTLR 文法结构中常见的自然遮蔽shadowing而非示例不足第五类则恰恰相反——代码路径存在且被执行只是工具无法单独计数。七、已知语法限制以下限制是作者有意为之的简化目的是让语法保持自包含、易维护代价是接受比严格 Scala 3 语法更宽泛的一小类输入。7.1importSelectors不支持混用命名选择器与通配选择器importSelectors : namedSelector (COMMA importSelectors)? | wildCardSelector (COMMA wildCardSelector)* ;合法 Scala 3 允许在一条 import 中混用命名选择器和通配符例如import foo.{bar, given, *}。上述规则只接受全部是namedSelector或全部是wildCardSelector两种列表不接受两者混排。该规则在 Scala3Parser.g4 L106-L109同时wildCardSelectorL100-L104支持*、迁移模式下的_以及given [Type]三种形式。7.2wildCardSelector、negation、variance用Op匹配单字符运算符词法器不为*、、-单独设 token——所有连续运算符字符都被合并为一个OptokenScala3Lexer.g4 L215-L217。因此下面三条规则都用Op代替仅允许的特定单字符RuleIntended operatorAlso accepted (over-broadly)wildCardSelector : OpL101*导入通配符任意运算符序列negation : OpL147数值字面量前的-任意运算符序列variance : OpL611类型参数型变的或-任意运算符序列文档指出若要严格化就需要新增STAR/MINUS/PLUS等单字符 token而这会迫使运算符词法在整个语法中碎片化对一份参考语法而言不值得。每条规则的注释都记录了其意图中的限制如 negation 的注释标注must be-。八、结合示例验证这些边界examples/中的文件既是覆盖率语料也是理解边界行为的活教材41_indentation_syntax.scala 全面覆盖冒号缩进写法缩进体 class/object/trait、缩进 match、缩进 for/yield、extension (n: Int):冒号形式的扩展方法、缩进 enum、given ... with等25_wildcard_given_import.scala 覆盖wildCardSelector的given形式import scala.math.{given}、namedSelector AS USCOREArrayList as _以及super[Base]类限定符40_coverage_gaps.scala 顶部注释明确列出它要触达的目标blockStat importDecl、usingParamClause构造器、extMethods、funParamClause/typedFunParamtype DepFn (x: Int) String走funTypeArgs的第三替代、ascriptionx: String、givenConditional等——可作为阅读不可达表格时的对照用例注意其中funParamClause的覆盖尝试与第六节表格不可达的判定存在张力恰好说明同一构造在不同上下文中的可达性差异。九、运行与验证方式小结生成 测试在仓库根目录执行 Maven参考 pom.xmlantlr4-maven-plugin生成 Java/C# 解析器与 visitor/listenerantlr4test-maven-plugin自动以compilationUnit为入口跑完examples/下所有.scala文件覆盖率生成 C# 解析器后在Generated-CSharp目录执行dotnet trash cover ../examples/*.scala并查看cover.html迁移模式验证以--3.0-migration参数启动解析程序可用 2246.scala2 这类.scala2语料人工验证 Scala 2 兼容语法手动试跑仓库 grun.sh 与_scripts/antlr4-tools/提供通用的 ANTLR 工具链可结合Scala3Parser.g4的compilationUnit入口对单文件做 tree 输出。十、结语scala/scala3/是 grammars-v4 中少数需要同时处理缩进敏感语法与编译器级换行语义的语法之一。它把最困难的部分INDENT/DEDENT 注入、语句分隔判定下沉到词法基类并忠实复刻 Dotty 的handleNewLine逻辑用语义谓词支撑--3.0-migration迁移模式再用trcover把规则覆盖钉在 98.3%最后以文档形式公开承认五处过宽接受或结构不可达的取舍。对于想为 Scala 3 构建工具链、或想学习如何为缩进敏感语言编写 ANTLR 语法的读者这套语法及其 readme.md 是一份可直接参考的完整样例。赞分享编程语言编译器开发工具【免费下载链接】grammars-v4Grammars written for ANTLR v4; expectation that the grammars are free of actions.项目地址https://gitcode.com/gh_mirrors/gr/grammars-v4点击查看免费下载相关推荐使用 ANTLR v4 解析 EVM 字节码grammars-v4 中 evm-bytecode 语法的设计与实战使用 ANTLR v4 解析 EVM 字节码grammars v4 中 evm bytecode 语法的设计与实战 导读 本文围绕 grammars v4 h编程语言编译器开发工具grammars-v4 中的 HyperTalk 语法用 ANTLR v4 解析 HyperCard 脚本语言grammars v4 中的 HyperTalk 语法用 ANTLR v4 解析 HyperCard 脚本语言 导读 本文讲解 grammars v4 htt编程语言编译器开发工具grammars-v4 仓库中的 Swift 5 ANTLR4 语法设计、已知限制与测试调试指南grammars v4 仓库中的 Swift 5 ANTLR4 语法设计、已知限制与测试调试指南 本篇文章围绕 swift/swift5/README.md编程语言编译器开发工具上一篇BabyAI模仿学习实战如何用Bot生成演示训练AI智能体下一篇如何永久备份微信聊天记录WeChatMsg完整指南与实战教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考