资讯动态

Carbon 语言的 Unicode 源文件规范:UTF-8 编码、NFC 规范化与标识符设计解析

发布时间:2026/9/10 13:41:02 来源:尧图企业网站定制
Carbon 语言的 Unicode 源文件规范UTF-8 编码、NFC 规范化与标识符设计解析【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang导读本文以 Carbon Language 仓库中的提案 proposals/p000142-unicode-source-files.md 为核心系统讲解 Carbon 对源文件编码、Unicode 版本、字节序标记BOM、规范化形式NFC以及标识符/空白字符选词规则的设计决策。文章结合仓库词法分析器toolchain/lex的真实实现与测试用例说明这些语言级规范在编译器底层是如何落地以及当前尚未落地的。读完本文你将掌握 Carbon 源文件在磁盘上的字节级编码约定、为何强制要求 NFC 而非其他规范化形式、UAX#31 与 UAX#39 在标识符设计中的角色以及当前工具链对 Unicode 支持的真实状态与演进方向。问题背景为什么源文件编码是一个必须先定的语言决策源文件的便携使用与维护要求所有参与方对文件在磁盘上是如何编码的有一致的理解。Carbon 提案开篇就指出名字中允许哪些字符、什么构成空白是一个复杂领域项目自身并不期望拥有本地专家——因此必须借助 Unicode 标准与相关附件的既有成果。在 2020 年立项之初Unicode 已被 Unicode Consortium 维护为全行业文本信息交换的规范编码。其中Unicode 标准附录 31UAX#31Unicode Identifier and Pattern Syntax 为通用编程语言标识符提供了推荐规则框架。Carbon 的决策就是建立在上述标准之上。核心提案两个基础事实提案给出两个核心结论构成 Carbon 全部词法处理的地基Carbon 程序表示为一串 Unicode 码点code points即取值范围在0到10FFFF₁₆之间的整数其作为字符或非字符的含义由 Unicode 标准定义。Carbon 源文件以 UTF-8 编码并基于 Unicode 13.0当时的最新版本同时声明后续应随新版标准发布而评估升级。此外Carbon 标识符与空白的词法约定将遵循 Unicode Annex 31UAX#31。这一决策直接回应了便携性诉求同一份.carbon源文件在 Linux、macOS、Windows 上以相同的字节序列存在无论编辑工具如何选择编译器都能给出确定性的解读。细节展开从字节到码点再到令牌字符编码码点是第一公民在切分为令牌之前程序首先是一串 Unicode 码点。提案明确指出码点是0到10FFFF₁₆的整数——这与 UTF-8 / UTF-16 / UTF-32 编码无关是语言语义层面的抽象表示。它决定了词法层面对字符的理解是码点而非字节后续涉及码点的校验如\u{...}转义、字符串内容都以此为基础。源文件UTF-8 与可选 BOM程序文本可能来自多种载体交互式编程环境REPL、数据库、IDE 的内存缓冲区、命令行参数等。但Carbon 程序的规范表示canonical representation是磁盘文件系统中的字节序列这些文件必须使用 UTF-8 编码。文件可以带有一个可选的 UTF-8 BOM字节序标记即字节序列EF₁₆ BB₁₆ BF₁₆。若存在该前缀会被忽略。这是一个对 Windows 生态友好的务实决定详见下文备选方案。无论程序文本以何种方式存储处理的第一步都是至少在概念上将其转换为 Unicode 码点序列转换结果即一个 Carbon源文件source file。提案还预留了一个灵活性根据语言需要可能要求每个源文件都关联一个文件名即使它并非来自文件系统。规范化整个源文件必须处于 NFC规范化是提案中技术含量最高的部分。Carbon 源文件包括注释和字符串字面量必须处于 Unicode 规范化形式 CNFC。负责格式化源码的工具会把源文件转换为 NFC以满足该约束。提案指出选择 NFC 实际上是四个相互独立决策的组合决策维度选择理由等价类equivalence class使用**规范canonical**形式而非兼容compatibility形式或无规范化若不规范化预组合附加符号如é与字母组合附加符号如e U0301会被视为不同字符兼容形式则会把连字ligature如ffi与其分解序列视为等价——对等宽字体而言规范形式最可能让看起来相同的字符被认定为相同组合方式composition使用**组合composed**形式NFC而非分解形式NFD例如ō在组合形式下为单个码点 U014D在分解形式下为 U006F U0304 两个码点组合形式表示更紧凑约束方式要求源文件本身就处于该形式而非必要时才转换见下文备选方案的详细论证覆盖范围要求整个文件都被规范化而非仅标识符或标识符字符串字面量避免字符串内出现渲染相同但语义不同的隐形差异提案引用 UAX#15 与 UAX#31 的共同建议对于编程语言中的大小写敏感标识符应使用 Normalization Form C。这同时也是 C 社区正在走向的方向WG21 论文 P1949R6 正在为 C 标识符采纳 NFC 要求GCC 已对非 NFC 的 C 标识符发出警告意味着 C 开发者现有的编辑工具链天然支持 NFC 源文件的编写。标识符与空白字符以 UAX#31 为基准UAX#31 并不直接规定具体的词法规则而是提供一个框架允许语言在其中选择具体规则。该框架包含建议出现在标识符中的字符集大小写 ASCII 字母以及合理的非 ASCII 字母扩展其中部分字符被限制为不能作为标识符首字符举例说明边界如 U30EA片假名 RI被包含而 U2603雪人 ☃不被包含——尽管两者在 C20 中都被允许出现在标识符里空白字符分类全部 ASCII 空白字符加上若干非 ASCII 空白字符语言特定profile机制可调整基线字符集例如允许下划线出现在标识符中或把不换行空格纳入空白。提案刻意不在此规定具体的词法选择也不承诺在任何具体领域都不偏离 UAX#31但确立了原则以 UAX#31 为决策基础任何偏离都需要强有力的理由。同形字Homoglyphs问题UAX#31 建议的ID_Start/XID_Start/ID_Continue/XID_Continue字符集包含大量同形字或近同形字——它们在渲染上相同或极相似却被解释为不同字符。这个问题即使把字符集限制为 ASCII 也存在例如kBa11Offset与kBall0ffset在部分字体下极难分辨但更宽的字符集会成倍放大风险。提案提出了一种可能的处理思路仅作为弱指引具体规则不在本提案范围内在名字查找中增加限制——如果某个作用域中的名字查找无结果但同作用域内存在 UAX#39 定义的混淆标识符confusable identifier则程序 ill-formed。该思路的用途在于证明 UAX#31 的方案与至少一种同形字解决方案兼容。备选方案被否决的设计选择及其理由备选一仅允许 ASCII优点实现复杂度降低彻底规避规范化、同形字、文本方向性bidirectional text等问题语言语法与库名均不使用非 ASCII 字符保证所有库中的名字都能被所有开发者可靠键入关键字已是 ASCII 字母。缺点Carbon 项目的总体目标是提供一种**包容、欢迎inclusive and welcoming**的语言不允许开发者用自己的母语书写名字和注释至少对一部分开发者而言无法达成该目标若强制用转义序列书写非 ASCII 可打印字符引号字符串的可读性将大幅下降。最终拒绝 ASCII 限制这是 Carbon 社区包容性价值观的直接体现理由部分也再次确认宁可一开始就接受广泛的文字系统也不愿将潜在开发者拒之门外。备选二禁止 BOM优点实现上只有边际的简化。缺点多个主流编辑器尤其 Windows 平台会插入 UTF-8 BOM 并用它识别文件编码。若语言禁止 BOM这些编辑器保存的默认格式将无法直接作为 Carbon 源文件使用。最终接受并忽略可选 BOM。备选三采用其他规范化形式要求 NFD分解形式优点是有环境更自然地产生 NFD且 NFD 更均匀字符总是被最大程度分解而 NFC 中字符可能分解也可能不分解对拼写纠正、同形字检测、代码补全等算法处理更友好缺点是 C 标准与社区正走向 NFCWG21 正在采纳 NFC 要求、GCC 对非 NFC 标识符告警、W3C 推荐所有内容使用 NFC网页上的代码示例可能被 Web 编辑工具规范化为 NFC、NFC 在所有存在差异的场合编码都更小。不要求任何规范化按码点序列比较标识符C20 及以前的规则优点是这是既有规则缺点是这不是 C 近期的规划方向且同一字符的不同表示会产生不同的标识符——这在多数编程环境中是不可见的。不要求规范化而是由编译器自己规范化源码优点是从源文本角度无论何种形式都同等对待开发者无需保证编辑环境产生并保持正确的规范化形式缺点是实现成本显著更高规范化标识符比检测是否处于规范形式复杂得多grep等工具不做规范化、面对不一致的规范化代码库会不可靠不同编辑环境的不同规范化选择会导致源文件字节表示不稳定给版本控制系统和补丁带来麻烦且规范化字符串字面量内容会带来用户意外。提案还指出虽然高质量实现可以接受该成本以便更好地从错误中恢复并且 NFC 存在 NFC_QC 快速检测路径但如果非规范源文本在形式上是合法的那么这类转换就面临比仅用于错误恢复更严格的性能约束。仅要求标识符或标识符注释规范化而非整个文件优点是在注释中使用任意文本更自由字符串字面量也可以包含故意非规范化的文本缺点是字符串内会出现渲染相同但语义不同的隐形差异程序的语义可能因编辑环境不可见地自动规范化而改变自动规范化还会给变更引入虚假 diff且必须小心确保没有任何字符串或注释分隔符以某个码点序列的分解前缀结尾——否则同一源文件的不同规范化会得到不同的切词tokenize结果。理由与决策确认提案的 Rationale 部分给出了决策委员会视角的确认指定源文件编码是推进更高层词法问题的前提UTF-8 与 UAX#31 对 2020 年的新语言而言是无可争议的选择。要求 NFC 有充分理由尤其考虑到 C 生态WG21 / GCC正在同步走向 NFC工具链支持可预期。支持将同形字与混淆问题视为超出本提案范围并倾向于一开始就接受广泛的文字系统以欢迎尽可能广泛的社区同时假定隐蔽代码underhanded code问题可以在编译器之外得到充分应对。仓库实证词法分析器对 Unicode 的实际处理状态提案是语言设计文档那么当前仓库实现走到哪一步了以下是toolchain/lex中的真实证据——这既能印证规范的方向也如实呈现设计与实现的差距。标识符字符集当前仍是 ASCIIcharacter_set.h 定义了 Carbon 词法规则下的字符分类其文件头注释直接承认TODO: These definitions need to be updated to match whatever Unicode lexical rules we pick.当前实现中标识符首字符仅包含A–Z、a–zIsAlpha续字符包含数字IsAlnum下划线虽不是字母数字但多数情况下是合法的续字符空白仅包含空格、制表符与换行。这与提案描述的 UAX#31 愿景尚有距离。在 lex.cpp 的标识符扫描函数注释中实现状态被表述得更直白TODO: Currently, this code does not implement Carbons design for Unicode characters in identifiers. It does work on UTF-8 code unit sequences, but currently considers non-ASCII characters to be non-identifier characters.也就是说当前词法器能在 UTF-8 码元序列上工作但把非 ASCII 字符一律视为非标识符字符完整 Unicode 标识符支持仍是待办事项。性能敏感的扫描路径已为 UTF-8 预留尽管功能未完成词法器在性能敏感的标识符前缀扫描上已做了精心设计。lex.cpp 中的 x86-64 SIMD 实现采用基于 4 位半字节查找表LUT的方案其注释明确对每个输入字节首先测试高位是否指示一个 UTF-8 编码的 Unicode 字符……这些字节也会在 LUT 结果中产生伪零但我们独立跟踪它们可以忽略。当发现高位置位的字节即非 ASCII时SIMD 快路径会回退到标量代码标量路径同样通过IsIdByteTable终止扫描。这段代码的设计目标正如注释所说——热循环在保持优化的同时保留足够的信息以便未来加入 Unicode 处理而不破坏相关优化。非 ASCII 字节的调度策略lex.cpp 的令牌分发表构建逻辑显示了一个有意思的中间状态所有非 ASCII UTF-8 字节都被调度到标识符词法keyword-or-identifier路径注释解释为空白字符本应已被跳过剩余的有效 Unicode 字符只可能是标识符的一部分。这段代码可以接受或拒绝它们。结合ScanForIdentifierPrefix的 TODO可以推断当前实现走的是拒绝分支——但调度结构已经为未来接受 Unicode 标识符铺好了路。字符串与字符字面量中的 UTF-8 校验与标识符不同字符串/字符字面量中的 UTF-8 处理是已经实现并严格测试的。在 string_literal.cpp 中\u{HHHHHH}转义序列会被展开为 UTF-8 码元序列码点超过0x10FFFF时报UnicodeEscapeTooLarge错误落入0xD800–0xDFFF代理区时报UnicodeEscapeSurrogate错误随后通过 LLVM 的ConvertUTF32toUTF8以严格模式strictConversion转换注释断言有效码点转换为 UTF-8 不可能失败。字面量内容读取时对无效 UTF-8 给出诊断CharLiteralUnderflowincomplete UTF-8不完整的 UTF-8与CharLiteralInvalidUTF8invalid UTF-8 character见 string_literal.cpp。这些诊断行为有对应的测试文件佐证fail_char_literals_bad_encoding.carbon 精心构造了各种无效 UTF-8 序列——缺失尾随字节的前导字节0xC3、连续两个前导字节0xC3 0xC3、超出合法范围的编码、编码出高代理0xED 0xA0 0x80→ UD800与低代理0xED 0xBF 0xBF→ UDFFF的序列——并逐一断言对应的诊断输出。该文件注释也提醒操作者处理此文件时要小心它包含无效 UTF-8 序列。与 C 方向的呼应P1949R6提案两次引用 WG21 论文 P1949R6 作为佐证Carbon 选择基于 UAX#31 的标识符规则、以及要求 NFC都与 C 标准社区正在进行的变革方向一致。对开发者而言这意味着你在 C 工具链中积累的如何让编辑器保存规范形式源文件的经验可以直接迁移到 Carbon 工作流中。实践要点小结对于使用或实现 Carbon 工具的开发者本提案的直接可执行结论如下保存 Carbon 源文件时使用 UTF-8如果编辑器默认添加 BOMEF BB BF无需手动移除——编译器会忽略它。确保文件内容处于 NFC字符串字面量与注释同样受此约束Carbon 格式化工具会在必要时自动转换为 NFC但提交前最好保证一致以免产生由规范化差异引起的虚假 diff。标识符目前请使用 ASCII 字母、数字与下划线尽管语言设计采纳了 UAX#31 的宽字符集愿景当前 toolchain/lex 实现仍将非 ASCII 字符视为非标识符字符。字符串与字符字面量内的 Unicode 通过\u{HHHHHH}转义或直接 UTF-8 文本表达编译器会对超出0x10FFFF、代理区码点以及畸形 UTF-8 给出明确诊断。同形字防护是提案预留的后续话题可能借助 UAX#39 的混淆检测但当前不构成约束。结论proposals/p000142-unicode-source-files.md 确立了 Carbon 语言的字符级地基UTF-8 编码 可选忽略 BOM 全文件 NFC 规范化 基于 UAX#31 的标识符/空白框架。这是一组面向 2020 年代新语言零争议的选择且与 C 社区的演进方向P1949R6、GCC NFC 告警保持同步降低了开发者生态的迁移成本。仓库实现则呈现出一个诚实的过渡态字面量层的 UTF-8 校验已完备且有测试覆盖而标识符层的 Unicode 支持仍标注为 TODO且性能敏感的扫描路径已为它预留好结构——这为关注 Carbon 词法演进的读者提供了一个清晰的观察窗口。【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价