Lua Unicode

lua-users home
wiki

本文旨在回答 LuaFaq 中的问题

我可以使用 Unicode 字符串吗?或者 Lua 支持 Unicode 吗?

简而言之,既是也不是。Lua 对 Unicode 无感知,Lua 字符串是按字节计数的,因此只要您可以将 Unicode 字符串视为简单的字节序列,就可以完成操作。如果这不够用,则有一些扩展模块可以满足您的需求。您只需要弄清楚“支持 Unicode”的具体含义,并使用来自正确模块的适当抽象。Unicode 非常复杂。

一些问题包括

Unicode 字符串和 Lua 字符串

Lua 字符串是任意数量的字节序列(编译器的 C 字符,因此为 8 位或更大)。Lua 不保留任何值,包括 `NUL`,因此可以存储任意二进制数据,包括 Unicode 数据。

为了获得最佳效果,请使用编码,其 Unicode 代码单元不超过单个字节,这通常将您限制为 utf8。任何其他编码也可以存储,包括但不限于 `UTF-16`、`UTF-32` 及其各种大端/小端变体。

Lua 中字符串的输入和输出(使用 io 库)符合 C 的保证。`ANSI C` 仅要求 `stdio` 库以二进制模式处理任意数据。在文本模式下,运行时允许映射输入和输出以处理行尾约定,甚至处理与 C 不同的字符集。如果您使用的库期望您使用与实际编码不兼容的内部编码,并且它试图调整行尾约定甚至更改编码,那么您将陷入困境。

这可能会影响您对 Unicode 进行非二进制文件输入和输出的能力。`UTF-8` 可能很安全,因为它与 ASCII 兼容,并且从未将 ASCII 字符用作代码点的多字节编码的一部分。

所有现代系统在文本模式下仅对行尾进行最小的字节序列映射,unix 甚至不需要这样做,因此文本模式和二进制模式相同。

如果您的 Unicode 使用仅限于将字符串传递给支持 Unicode 的外部库,那么您应该没问题。例如,您应该能够从数据库中提取 Unicode 字符串并将其传递给支持 Unicode 的图形库。

Unicode Lua 程序

在您的 Lua 程序中可以使用字面 Unicode 字符串。您可以直接使用 UTF-8 编码的字符串,其中包含 8 位字符,或者可以使用 \ddd 语法(注意 ddd 是一个十进制数字,与其他一些语言不同)。但是,没有用于编码多字节序列(例如 \U+20B4)的工具;您需要手动将它们编码为 UTF-8,或者以正确的 大端/小端 顺序插入单个字节(对于 UTF-16UTF-32)。

除非您使用的是字符宽度超过 8 位的操作系统,否则您将无法在 Lua 标识符(变量名等)中使用任意 Unicode 字符。您可能可以使用 ANSI 范围之外的 8 位字符。Lua 使用 C 函数 isalphaisalnum 来识别标识符中的有效字符,因此它将取决于当前区域设置。老实说,在 Lua 标识符中使用 ANSI 范围之外的字符不是一个好主意,因为您的程序将无法在标准 C 区域设置中编译。

比较和排序

Lua 字符串比较(使用 == 运算符)是逐字节进行的。这意味着 == 只能用于比较 Unicode 字符串的相等性,前提是字符串已在四种 Unicode 规范化之一中进行了规范化。(有关详细信息,请参阅 [Unicode 规范化常见问题解答]。)标准 Lua 库不提供任何用于规范化 Unicode 字符串的工具。因此,未规范化的 Unicode 字符串不能可靠地用作表键。

如果您想使用 Unicode 字符串相等的概念,或使用 Unicode 字符串作为表键,并且您无法保证您的字符串已规范化,那么您将不得不找到一个规范化函数并使用它;编写一个这样的函数是一项非凡的任务!

Lua 字符串上的比较运算符(<<=)使用 C 函数 strcoll,该函数依赖于区域设置。这意味着两个字符串的比较方式会根据当前区域设置而有所不同。例如,使用西班牙传统排序和使用威尔士语排序时,字符串的比较方式将不同。

您的操作系统可能有一个实现您想要的排序算法的区域设置,在这种情况下,您可以直接使用它,否则您将不得不编写一个函数来对 Unicode 字符串进行排序。这将是一项更加非凡的任务。

UTF-8 的设计初衷是,对字节序列进行简单的逐字节字符串比较将产生与对代码点进行逐代码点比较相同的结果。UTF-32BE 也是如此,但我不知道有任何系统使用这种编码。不幸的是,简单的逐字节比较不是任何语言使用的排序顺序。

(注意:有时人们使用UCS-2UCS-4来表示“双字节”和四字节编码。这些不是 Unicode 标准;它们来自密切相关的ISO标准ISO/IEC 10646-1:2000,目前的区别在于它们允许 Unicode 范围之外的代码,Unicode 范围从0x00x10FFFF。)

模式匹配

Lua 的模式匹配功能逐字节工作。一般来说,这将不适用于 Unicode 模式匹配,尽管有些事情会按您的意愿工作。例如,"%u" 不会匹配所有 Unicode 大写字母。您可以在规范化的 Unicode 字符串中匹配单个 Unicode 字符,但您可能需要担心组合字符序列。如果没有后续的组合字符,"a" 将只匹配 UTF-8 字符串中的字母 a。在 UTF-16LE 中,您可以匹配 "a\0"

长度和字符串索引

如果您使用 Unicode 字符串,至少有五种不同的长度概念。注意不要使用错误的长度概念。

例如,您可以使用以下代码片段来计算您知道符合标准的字符串中的 UTF-8 字符(它会错误地计算一些无效字符)

        local _, count = string.gsub(unicode_string, "[^\128-\193]", "")

如果您想知道当您使用固定宽度字体打印 Unicode 字符串时,它将占用多少打印列(假设您正在编写类似 Unix ls 程序的东西,它将输出格式化为几列),那么答案又不同了。这是因为一些 Unicode 字符没有打印宽度,而另一些则是双宽度字符。组合字符用于为其他字母添加重音,并且通常在打印时不会占用任何额外的空间。

您可以使用以下代码片段来迭代 UTF-8 序列(这将简单地跳过大多数无效代码)

        for uchar in string.gmatch(ustring, "([%z\1-\127\194-\244][\128-\191]*)") do
          -- something
        end

UTF8 解码函数

--[[
| bits | U+first   | U+last     | bytes | Byte_1   | Byte_2   | Byte_3   | Byte_4   | Byte_5   | Byte_6   |
+------+-----------+------------+-------+----------+----------+----------+----------+----------+----------+
|   7  | U+0000    | U+007F     |   1   | 0xxxxxxx |          |          |          |          |          |
|  11  | U+0080    | U+07FF     |   2   | 110xxxxx | 10xxxxxx |          |          |          |          |
|  16  | U+0800    | U+FFFF     |   3   | 1110xxxx | 10xxxxxx | 10xxxxxx |          |          |          |
|  21  | U+10000   | U+1FFFFF   |   4   | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |          |          |
| *26  | U+200000  | U+3FFFFFF  |   5   | 111110xx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |          |
| *31  | U+4000000 | U+7FFFFFFF |   6   | 1111110x | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
--]]
* UTF8 was restricted to 4 bytes, because UTF16 surrogates enable a maximum of BMP+16 astral planes -> not quite 21 bit.

此函数将包含 UTF-8 编码字符的 Lua 字符串转换为包含其对应 Unicode 代码点(UTF-32)的 Lua 表格。

function Utf8to32(utf8str)
	assert(type(utf8str) == "string")
	local res, seq, val = {}, 0, nil
	for i = 1, #utf8str do
		local c = string.byte(utf8str, i)
		if seq == 0 then
			table.insert(res, val)
			seq = c < 0x80 and 1 or c < 0xE0 and 2 or c < 0xF0 and 3 or
			      c < 0xF8 and 4 or --c < 0xFC and 5 or c < 0xFE and 6 or
				  error("invalid UTF-8 character sequence")
			val = bit32.band(c, 2^(8-seq) - 1)
		else
			val = bit32.bor(bit32.lshift(val, 6), bit32.band(c, 0x3F))
		end
		seq = seq - 1
	end
	table.insert(res, val)
	table.insert(res, 0)
	return res
end

更复杂的问题

正如您可能已经猜到的那样,Lua 不支持双向打印或泰国音调的正确格式化。通常,这些问题将由图形或排版库处理。当然,如果您有权访问这样的库,则可以与执行这些操作的库进行交互。

注意:从 Lua 5.3 开始,有一个名为“utf8”的内置模块。其中一些模块也称为“utf8”,会导致名称冲突。请在不同的名称下要求它们。

请参阅 Unicode标识符 以获取平台无关的 Unicode Lua 程序。

另请参阅


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2020 年 5 月 11 日下午 10:42 GMT (差异)