Raku 对 Unicode 具有高度支持,最新版本支持 Unicode 15.0。本文档旨在概述并描述不属于例程和方法文档的 Unicode 功能。

虽然它是 Rakudo 使用的 VM 的一部分,但这份关于 MoarVM 字符串内部表示的 概述 提供了一些有趣的细节。

出于安全原因,浏览器(例如:Firefox)可能会限制显示不在受信任字体中的 Unicode 字符,即使操作系统安装了显示该字符的字体。要解决 Firefox 的此问题,请将 `privacy.fingerprintingProtection` 配置选项设置为 `False`。

文件句柄和 I/O§

规范化§

Raku 默认情况下对所有输入和输出应用规范化,除了文件名,文件名以 UTF8-C8 格式读取和写入;字素(grapheme)是字符的用户可见形式,将使用规范化的表示形式。例如,字素 á 可以用两种方式表示,一种是用一个码点

á (U+E1 "LATIN SMALL LETTER A WITH ACUTE")

或者两个码点

a +  ́ (U+61 "LATIN SMALL LETTER A" + U+301 "COMBINING ACUTE ACCENT")

Raku 会将这两个输入都转换为一个码点,如规范化形式 C (NFC) 所指定。在大多数情况下,这很有用,这意味着两个等效的输入都会被视为相同。Unicode 有一个规范等效的概念,它允许我们确定字符串的规范形式,从而允许我们正确地比较字符串并操作它们,而不必担心文本丢失这些属性。默认情况下,您从 Raku 处理或输出的任何文本都将以这种“规范”形式存在,即使对字符串进行修改或连接也是如此(有关如何避免这种情况,请参见下文)。有关规范化形式 C 和规范等效的更多详细信息,请参阅 Unicode 基金会关于 规范化和规范等效 的页面。

我们不默认使用规范化的一种情况是文件名。这是因为文件名必须完全按照磁盘上写入的字节进行访问。

要避免规范化,您可以使用一种称为 UTF8-C8 的特殊编码格式。在任何文件句柄上使用此编码将允许您读取磁盘上的确切字节,而不会进行规范化。如果您使用 UTF8 句柄打印它们,它们看起来可能很奇怪。如果您将其打印到输出编码为 UTF8-C8 的句柄,那么它将按预期呈现,因为它是逐字节的精确副本。有关 MoarVM 上 UTF8-C8 的更多技术细节,请参见下文。

UTF8-C8§

UTF-8 Clean-8 是一种编码器/解码器,它主要作为 UTF-8 编码器/解码器工作。但是,在遇到无法解码为有效 UTF-8 的字节序列,或者由于规范化而无法进行往返的字节序列时,它将使用 NFG 合成 来跟踪所涉及的原始字节。这意味着编码回 UTF-8 Clean-8 将能够重新创建字节,就像它们最初存在一样。合成包含四个码点

  • 码点 0x10FFFD(这是一个私有使用码点)

  • 码点 'x'

  • 不可解码字节的高 4 位作为十六进制字符 (0..9A..F)

  • 低 4 位作为不可解码字节的十六进制字符 (0..9A..F)

在正常的 UTF-8 编码下,这意味着不可表示的字符将显示为类似 ?xFF 的内容。

UTF-8 Clean-8 用于 MoarVM 从环境、命令行参数和文件系统查询接收字符串的地方;例如,在解码缓冲区时

say Buf.new(ord('A'), 0xFEord('Z')).decode('utf8-c8');
#  OUTPUT: «A􏿽xFEZ␤»

您可以看到 UTF8-C8 使用的两个初始码点在 'FE' 之前显示。您可以使用这种类型的编码来读取编码未知的文件

my $test-file = "/tmp/test";
given open($test-file:w:bin{
  .write: Buf.new(ord('A'), 0xFAord('B'), 0xFB0xFCord('C'), 0xFD);
  .close;
}
 
say slurp($test-fileenc => 'utf8-c8');
# OUTPUT: «(65 250 66 251 252 67 253)␤»

使用这种类型的编码读取它们并将其编码回 UTF8-C8 将为您提供原始字节;使用默认的 UTF-8 编码将无法实现这一点。

请注意,这种编码目前在 Rakudo 的 JVM 实现中不受支持。

输入 Unicode 码点和码点序列§

您可以通过数字(十进制和十六进制)输入 Unicode 码点。例如,名为“带长音的拉丁大写字母 ae”的字符的十进制码点为 482,十六进制码点为 0x1E2

say "\c[482]"# OUTPUT: «Ǣ␤» 
say "\x1E2";   # OUTPUT: «Ǣ␤»

您也可以通过名称访问 Unicode 码点:Raku 支持所有 Unicode 名称。

say "\c[PENGUIN]"# OUTPUT: «🐧␤» 
say "\c[BELL]";    # OUTPUT: «🔔␤» (U+1F514 BELL)

所有 Unicode 码点名称/命名序列/表情符号序列现在不区分大小写:[从 Rakudo 2017.02 开始]

say "\c[latin capital letter ae with macron]"# OUTPUT: «Ǣ␤» 
say "\c[latin capital letter E]";              # OUTPUT: «E␤» (U+0045)

您可以使用逗号分隔的列表以及 \c[] 来指定多个字符。您也可以将数字和命名样式结合起来

say "\c[482,PENGUIN]"# OUTPUT: «Ǣ🐧␤»

除了在插值字符串中使用 \c[] 之外,您还可以使用 uniparse

say "DIGIT ONE".uniparse;  # OUTPUT: «1␤» 
say uniparse("DIGIT ONE"); # OUTPUT: «1␤»

有关以相反方向(单个码点和多个码点)工作的例程,请参见 uninameuninames

名称别名§

名称别名主要用于没有官方名称的码点、缩写或更正(Unicode 名称永远不会改变)。有关它们的完整列表,请参见 此处

没有官方名称的控制代码

say "\c[ALERT]";     # Not visible (U+0007 control code (also accessible as \a)) 
say "\c[LINE FEED]"# Not visible (U+000A same as "\n")

更正

#   Correct name as input: 
say                     "\c[LATIN CAPITAL LETTER GHA]"# OUTPUT: «Ƣ␤» 
#   Original, erroneous name as output: 
say "Ƣ".uniname# OUTPUT: «LATIN CAPITAL LETTER OI␤» 
 
# This one is a spelling mistake that was corrected in a Name Alias: 
#   Correct name as input: 
say    "\c[PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRACKET]".uniname;
#   Original, erroneous name as output: 
# OUTPUT: «PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET␤»

缩写

say "\c[ZWJ]".uniname;   # OUTPUT: «ZERO WIDTH JOINER␤» 
say "\c[NBSP]".uniname;  # OUTPUT: «NO-BREAK SPACE␤» 
say "\c[NNBSP]".uniname# OUTPUT: «NARROW NO-BREAK SPACE␤»

命名序列§

您也可以使用任何 命名序列,这些不是单个码点,而是它们的序列。[从 Rakudo 2017.02 开始]

say "\c[LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND ACUTE]";      # OUTPUT: «É̩␤» 
say "\c[LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND ACUTE]".ords# OUTPUT: «(201 809)␤»

表情符号序列§

Raku 支持表情符号序列。有关所有表情符号序列,请参见:表情符号 ZWJ 序列表情符号序列。请注意,任何包含逗号的名称都应删除逗号,因为 Raku 使用逗号来分隔同一 \c 序列中的不同码点/序列。

say "\c[woman gesturing OK]";         # OUTPUT: «🙆‍♀️␤» 
say "\c[family: man woman girl boy]"# OUTPUT: «👨‍👩‍👧‍👦␤»

混淆性§

由于 Unicode 中有大量的字形,更广泛的支持意味着您可能会发现一些容易混淆的字形。有关如何避免问题的通用提示,请参阅 unicode.org 网站上的 混淆性