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 代碼單元不超過一個位元組的編碼,這通常會將你限制為 UTF-8。也可以儲存其他任何編碼,包含但不限於 UTF-16、UTF-32,以及它們的各種大尾/小尾變體。

使用 Lua 輸入和輸出字串(使用 io 函式庫)時符合 C 的保證。ANSI C 只需要 stdio 函式庫即可在二進位模式中處理任意資料。在文字模式中,執行階段可以在輸入和輸出中對應,以處理行尾慣例甚至 C 以外的字元集。如果函式庫預期你要使用與實際編碼不相容的內部編碼,且它嘗試調整行尾慣例或甚至變更編碼,那你就慘了。

這可能會影響你執行非二進位檔案的 Unicode 輸入和輸出。UTF-8 可能很安全,因為它與 ASCII 相容,且絕不會在碼位的多位元組編碼中使用 ASCII 字元。

所有現代系統只針對文字模式中的行尾執行最小的位元組序列對應,類 Unix 系統甚至不需要這樣做,因此使文字模式與二進位模式相同。

如果使用 Unicode 是為了將字串傳遞給支援 Unicode 的外部函式庫,你應該會相安無事。例如,你應該能夠從資料庫中擷取一個 Unicode 字串並傳遞給 Unicode 感知的圖形函式庫。

Unicode Lua 程式

文學的 Unicode 字串會出現在你的 lua 程式中。一個 `UTF-8` 編碼的字串可以使用 8 位元字元直接出現,或者你可以使用 `\ddd` 語法(請注意 `ddd` 是 _十進位_ 數字,和其他語言不同)。不過並沒有一個可供編碼多八位元組序列的工具(例如 `\U+20B4`);你需要手動將它們編碼到 `UTF-8`,或插入個別八位元組到正確的大端/小端順序(針對 `UTF-16` 或 `UTF-32`)。

除非你使用的是一個作業系統,其中 `char` 的位元寬度大於八個位元,否則你不能在 Lua 標示符(變數的名稱等等)中使用任意的 Unicode 字元。你可能會在 `ANSI` 範圍之外使用八位元字元。Lua 使用 `C` 函式 `isalpha` 和 `isalnum` 辨識標示符中的有效字元,因此這將會取決於目前的區域設定。說真的,在 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,目前的不同之處在於,它們允許碼點超出從0x00x10FFFF的 Unicode 範圍。)

模式比對

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 字串轉換為 lua 表格,其對應的 unicode 編碼點為 (UTF-32)

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」,會導致名稱衝突。請在不同的名稱下需要它們。

請參閱UnicodeIdentifers,深入了解與平台無關的 Unicode Lua 程式。

另請參閱


RecentChanges喜好設定
編輯歷程記錄
上次於 2020 年 5 月 11 日晚上 10:42 編輯 (GMT) (diff)