查看源代码 Unicode 语法

Elixir 在整个语言中都支持 Unicode。本文档是 Elixir 如何在其语法中支持 Unicode 的完整参考。

字符串("olá")和字符列表('olá')从 Elixir v1.0 开始支持 Unicode。字符串使用 UTF-8 编码。字符列表是 Unicode 代码点的列表。在这种情况下,内容将按开发人员编写的方式保留,不会进行任何转换。

从 Elixir v1.5 开始,Elixir 还支持变量、原子和调用中的 Unicode。本文档的重点是介绍 Elixir 如何在其语法中允许使用 Unicode 的高级概述。我们还提供了技术文档,描述 Elixir 如何符合 Unicode 规范。

要检查当前 Elixir 安装的 Unicode 版本,请运行 String.Unicode.version()

简介

Elixir 允许在其变量、原子和调用中使用 Unicode 字符。但是,Unicode 字符必须仍然遵守语言语法的规则。特别是,变量和调用不能以大写字母开头。从现在开始,我们将这些术语称为标识符。

标识符中允许的字符是 Unicode 规定的字符。一般来说,它仅限于当前仍在使用的语言书写系统通常使用的字符。特别是,它排除了诸如表情符号、替代数字表示法、音符等符号。

Elixir 对标识符施加了许多限制,以确保安全性。例如,单词“josé”可以用两种方式用 Unicode 写:作为字符 j o s é 的组合,以及作为字符 j o s e ́ 的组合,其中重音符号是其自身的字符。前者称为 NFC 形式,后者为 NFD 形式。Elixir 将所有字符标准化为 NFC 形式。

Elixir 还禁止大多数情况下使用混合脚本。例如,无法将变量命名为 аdmin,其中 а 为西里尔字母,其余字符为拉丁字母。这样做会引发以下错误

** (SyntaxError) invalid mixed-script identifier found: аdmin

Mixed-script identifiers are not supported for security reasons. 'аdmin' is made of the following scripts:

  \u0430 а {Cyrillic}
  \u0064 d {Latin}
  \u006D m {Latin}
  \u0069 i {Latin}
  \u006E n {Latin}

Make sure all characters in the identifier resolve to a single script or a highly
restrictive script. See https://hexdocs.cn/elixir/unicode-syntax.html for more information.

该字符必须全部为西里尔字母或全部为拉丁字母。根据高度限制性 Unicode 建议,Elixir 允许的唯一混合脚本是

  • 拉丁语和汉语带注音
  • 拉丁语和日语
  • 拉丁语和韩语

最后,Elixir 还会在同一文件中对可混淆的标识符发出警告。例如,如果您在代码中同时使用变量 а(西里尔字母)和 а(拉丁字母),Elixir 会发出警告。

这就是 Unicode 如何在 Elixir 标识符中使用的整体介绍。简而言之,它的目标是支持当今使用的不同书写系统,同时保持 Elixir 语言本身清晰安全。

有关技术细节,请参阅下一部分,其中涵盖了技术 Unicode 要求。

Unicode 附录 #31

Elixir 实现了 Unicode 附录 #31 版本 15.0 中概述的要求。

R1. 默认标识符

一般 Elixir 标识符规则指定为

<Identifier> := <Start> <Continue>* <Ending>?

其中 <Start> 使用与规范相同的类别,但将其标准化为 NFC 形式(见 R4)

从 Unicode 通用类别的大写字母、小写字母、首字母大写字母、修饰符字母、其他字母、字母数字派生的字符,以及 Other_ID_Start,减去 Pattern_SyntaxPattern_White_Space 代码点。

在集合符号中:[\p{L}\p{Nl}\p{Other_ID_Start}-\p{Pattern_Syntax}-\p{Pattern_White_Space}]

以及 <Continue> 使用与规范相同的类别,但将其标准化为 NFC 形式(见 R4)

ID_Start 字符,加上具有 Unicode 通用类别非间隔标记、间隔组合标记、十进制数字、连接标点符号的字符,以及 Other_ID_Continue,减去 Pattern_SyntaxPattern_White_Space 代码点。

在集合符号中:[\p{ID_Start}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Other_ID_Continue}-\p{Pattern_Syntax}-\p{Pattern_White_Space}]

<Ending> 是 Elixir 特有的补充,它仅包含代码点 ? (003F) 和 ! (0021)。

该规范还提供了一个 <Medial> 集合,但 Elixir 不包括该集合中的任何字符。因此,标识符规则已简化为考虑这一点。

Elixir 不允许在标识符中使用 ZWJ 或 ZWNJ,因此不实现 R1a。双向控制字符也不受支持。出于向后兼容性目的,保证 R1b。

原子

Elixir 中的 Unicode 原子遵循上述标识符规则,并进行以下修改

  • <Start> 还包括代码点 _ (005F)
  • <Continue> 还包括代码点 @ (0040)

请注意,原子也可以用引号括起来,这允许使用任何字符,例如 :"hello elixir"。所有 Elixir 运算符也是有效的原子,例如 :+:@:|> 等。有效原子的完整描述在 "语法参考中的“原子”部分" 中提供。

变量、本地调用和远程调用

Elixir 中的变量遵循上述标识符规则,并进行以下修改

  • <Start> 还包括代码点 _ (005F)
  • <Start> 还排除 Lu(大写字母)和 Lt(首字母大写字母)字符

在集合符号中:[\u{005F}\p{Ll}\p{Lm}\p{Lo}\p{Nl}\p{Other_ID_Start}-\p{Pattern_Syntax}-\p{Pattern_White_Space}]

别名

Elixir 中的别名仅允许 ASCII 字符,以大写字母开头,并且没有标点符号。

R3. Pattern_White_Space 和 Pattern_Syntax 字符

Elixir 仅支持代码点 \t (0009)、\n (000A)、\r (000D) 和 \s (0020) 作为空格,因此不遵循要求 R3。R3 要求支持更多种类的空格和语法字符。

R4. 等效标准化标识符

Elixir 中的标识符区分大小写。

Elixir 将所有原子和变量标准化为 NFC 形式。但是,引号括起来的原子和字符串可以采用任何形式,并且不会被解析器验证。

换句话说,原子 :josé 只能用代码点 006A 006F 0073 00E9006A 006F 0073 0065 0301 编写,但 Elixir 会将其改写为前者(从 Elixir 1.14 开始)。另一方面,:"josé" 可以写成 006A 006F 0073 00E9006A 006F 0073 0065 0301,并且其形式将被保留,因为它是在引号之间编写的。

选择要求 R4 会自动排除要求 R5、R6 和 R7。

Unicode 技术标准 #39

Elixir 符合 Unicode 技术标准 #39 版本 15.0 中概述的条款,该标准关于安全性。

C1. 标识符的一般安全配置文件

Elixir 将不允许使用 \p{Identifier_Status=Restricted} 中的代码点对标识符进行标记。

遵循一般安全配置文件的实现不允许 \p{Identifier_Status=Restricted} 中的任何字符,...

例如,'HANGUL FILLER' () 字符通常是不可见的,它是一个不常见的代码点,会触发此警告。

请参阅下面有关其他规范化的说明,这些规范化可以对某些受限制的标识符进行自动替换。

C2. 可混淆检测

Elixir 将对看起来相同但不同的标识符发出警告。例如:在 а = a = 1 中,两个“a”字符分别是西里尔字母和拉丁字母,可能会相互混淆;在 力 = カ = 1 中,两者都是日语,但代码点不同,属于该书写系统的不同脚本。可混淆的标识符会导致难以发现的错误(例如,由于复制粘贴的代码),并且可能不安全,因此我们将对同一个文件中可能相互混淆的标识符发出警告。

我们使用第 4 节“可混淆检测”中描述的方法,并进行了一项修改

或者,它应声明使用修改,并提供一个精确的字符映射列表,这些列表是在提供的字符映射列表中添加或删除的。

Elixir 不会对仅由 a-z、A-Z、0-9 和 _ 组成的标识符的可混淆性发出警告。这是因为 ASCII 标识符已经存在了很长时间,程序员社区已经有自己的方法来处理诸如 l,1O,0 之类的标识符之间的可混淆性(例如,专为编程设计的字体通常可以轻松区分这些字符)。

C3. 混合脚本检测

Elixir 将不允许对混合脚本标识符进行标记,除非混合是 UTS 39 5.2 “高度限制性”中定义的例外之一。我们使用第 5.1 节“混合脚本检测”中描述的方法来确定是否正在发生脚本混合,并对“其他规范化”部分中记录的修改进行修改。

例如:Elixir 允许像 幻ㄒㄧㄤ 这样的标识符,即使它包含来自多个“脚本”的字符,因为当应用 UTS 39 5.1 中的解析规则时,这些脚本都“解析”为日语。它还允许像 :Tシャツ 这样的原子,它是日语中的“T 恤”,它包含一个拉丁大写 T,因为 {Latn, Jpan} 是 UTS 39 5.2 中“高度限制性”定义中允许的脚本混合之一,并且它“涵盖”了该字符串。

但是,Elixir 将阻止对类似 if аdmin, do: :ok, else: :err 的代码进行标记,其中“a”字符的脚本集为 {Cyrillic},而所有其他字符的脚本集为 {Latin}。脚本集无法解析,并且 UTS 39 5.2 中“高度限制性”定义中的脚本集也无法涵盖该字符串,因此将显示描述性错误。

C4、C5(不适用)

不主张“C4 - 限制级别检测”的一致性,并且它不适用于代码中的标识符;相反,它适用于将给定任意字符串的安全性级别分类为 5 个限制级别中的一个。

“C5 - 混合数字检测”的一致性不适用,因为 Elixir 不支持 Unicode 数字。

添加规范化和已记录的 UTS 39 修改

从 Elixir 1.14 开始,\p{Identifier_Status=Restricted} 中的一些代码点将标准化为其他不受限制的代码点。

最初,这只是为了将 MICRO SIGN µ 转换为希腊小写 mu,μ

这不是对 UTS39 条款 C1(通用安全配置文件)或 C2(混淆检测)的修改;然而,它是对 C3,“混合脚本检测”的已记录修改。

混合脚本检测通过这些归一化进行修改,归一化的码点被赋予来自两个字符的脚本集的并集。

  • 例如,在 MICRO => MU 的示例中,Micro 是一个“Common”脚本字符 - 与“_”下划线码点相同的脚本 - 因此归一化字符的脚本集将是 {Greek, Common}。'Common' 与所有非空脚本集相交,因此归一化字符可以在任何脚本编写的令牌中使用,而不会导致脚本混合。

  • 以这种方式归一化的码点是那些在社区中使用的,并且被判断不太可能导致不安全的脚本混合问题。例如,MICRO 或 MU 码点可以用于处理微秒的原子或变量。