Để nhất quán trong việc xử lý tiếng Việt, chúng ta thống nhất ý nghĩa của các thuật ngữ sau trong Bogo-logic:
-
Dấu thanh là các dấu thay đổi thanh điệu của nguyên âm. Tiếng Việt có 6 loại dấu thanh:
-
Thanh ngang (không dấu)
-
Thanh huyền (grave)
-
Thanh sắc (acute)
-
Thanh hỏi (hook)
-
Thanh ngã (tilde)
-
Thanh nặng (dot)
-
local TONE_GRAVE,
TONE_ACUTE,
TONE_HOOK,
TONE_TILDE,
TONE_DOT,
TONE_NONE = 1, 2, 3, 4, 5, 6
-
Dấu chữ cái là các dấu để tạo chữ cái mới từ chữ cái sẵn có:
-
Dấu mũ (hat, như trong chữ cái â)
-
Dấu móc (horn, như trong chữ cái ơ)
-
Dấu trăng (breve, như trong chữ cái ă)
-
Dấu ngang (dash, như trong chữ cái đ)
-
local MARK_BREVE,
MARK_HAT,
MARK_HORN,
MARK_DASH,
MARK_NONE = 1, 2, 3, 4, 5
-
Nguyên âm (vowel) là một âm thanh trong ngôn ngữ nói, được phát âm với thanh quản mở. Ví dụ: a, e, u, ư, uyê, …
Phụ âm (consonant) là một âm thanh trong ngôn ngữ nói, được phát âm với thanh quản đóng. Ví dụ: b, c, ph, tr, …
Warning
|
Khi ký âm (chuyển từ âm thanh sang chữ viết), nguyên âm hay phụ âm có thể được tạo thành từ một hoặc nhiều chữ cái. Khái niệm nguyên âm, phụ âm thường bị hiểu sai là một chữ cái đơn lẻ. |
Note
|
Khi nói về nguyên âm hoặc phụ âm chỉ được cấu tạo bởi một chữ cái, chúng tôi sẽ dùng chữ cái nguyên âm và chữ cái phụ âm tương ứng. |
-
Âm tiết (syllable) là đơn vị cấu tạo từ cơ bản. Ví dụ:
-
Từ nhà có một âm tiết: nhà.
-
Từ la-tinh có 2 âm tiết: la và tinh.
-
Từ hợp tác xã có 3 âm tiết: hợp, tác, và xã.
-
Như mọi bộ gõ khác, phần quan trọng nhất của Bogo-logic là xử lý tiếng Việt. Công việc này được chia thành 2 thao tác lớn:
-
Thêm dấu chữ cái
-
Thêm dấu thanh
Để thêm dấu thanh, trước hết chúng ta sẽ định nghĩa một bảng tra cứu tuần hoàn tất cả các chữ cái nguyên âm trong tiếng Việt với tất cả các tổ hợp dấu thanh có thể.
local VOWEL_CHARS = {
"à", "á", "ả", "ã", "ạ", "a", "ằ", "ắ", "ẳ", "ẵ", "ặ", "ă",
"ầ", "ấ", "ẩ", "ẫ", "ậ", "â", "è", "é", "ẻ", "ẽ", "ẹ", "e",
"ề", "ế", "ể", "ễ", "ệ", "ê", "ì", "í", "ỉ", "ĩ", "ị", "i",
"ò", "ó", "ỏ", "õ", "ọ", "o", "ồ", "ố", "ổ", "ỗ", "ộ", "ô",
"ờ", "ớ", "ở", "ỡ", "ợ", "ơ", "ù", "ú", "ủ", "ũ", "ụ", "u",
"ừ", "ứ", "ử", "ữ", "ự", "ư", "ỳ", "ý", "ỷ", "ỹ", "ỵ", "y"
}
Để ý kỹ có thể thấy ngay các chữ cái nguyên âm trong bảng được sắp xếp theo trật tự huyền, sắc, hỏi, ngã, nặng, ngang, đúng với trật tự chúng ta đã định nghĩa các mã dấu thanh ở trên. Nhờ vậy, chúng ta có thể định nghĩa thuật toán thêm dấu thanh đơn giản là tìm vị trí nhóm nguyên âm tương ứng với nguyên âm cần thêm dấu trong bảng và sử dụng mã dấu thanh cần thêm làm tọa độ tương đối so với nhóm.
-- include::vowel-chars-defs
function add_tone_char (char, tone)
for i, v in pairs(VOWEL_CHARS) do
if char == v then
return VOWEL_CHARS[math.floor((i - 1) / 6) * 6 + tone]
end
end
return char
end
describe("add_tone_char", function ()
it("adds a tone to a char", function ()
assert.are.equal(add_tone_char ("a", TONE_ACUTE), "á")
assert.are.equal(add_tone_char ("ỷ", TONE_HOOK), "ỷ")
assert.are.equal(add_tone_char ("ụ", TONE_NONE), "u")
end)
end)
Việc thêm dấu chữ cái cũng cơ bản chỉ là thao tác tra cứu bảng. Chúng ta định nghĩa một bảng chứa tất cả các chữ cái có thể thêm dấu chữ cái và kết quả khi thêm dấu. Tuy nhiên, để đơn giản, chúng ta sẽ chỉ định nghĩa phiên bản không dấu thanh của các chữ cái đó. Thay vì định nghĩa hết, chúng ta sẽ lưu dấu thanh của chữ cái đầu vào, xóa dấu đó đi, tra bảng để thêm dấu chữ cái, sau đó trả lại dấu thanh như ban đầu.
-- include::get-tone-char
function add_mark_char (char, mark)
local tone = get_tone_char(char)
if tone ~= TONE_NONE then
char = add_tone_char(char, TONE_NONE)
end
local mapping = {
["a"] = {[MARK_HAT] = "â", [MARK_BREVE] = "ă", [MARK_NONE] = "a"},
["ă"] = {[MARK_HAT] = "â", [MARK_BREVE] = "ă", [MARK_NONE] = "a"},
["â"] = {[MARK_HAT] = "â", [MARK_BREVE] = "ă", [MARK_NONE] = "a"},
["e"] = {[MARK_HAT] = "ê", [MARK_NONE] = "e"},
["ê"] = {[MARK_HAT] = "ê", [MARK_NONE] = "e"},
["o"] = {[MARK_HAT] = "ô", [MARK_HORN] = "ơ", [MARK_NONE] = "o"},
["ô"] = {[MARK_HAT] = "ô", [MARK_HORN] = "ơ", [MARK_NONE] = "o"},
["ơ"] = {[MARK_HAT] = "ô", [MARK_HORN] = "ơ", [MARK_NONE] = "o"},
["u"] = {[MARK_HORN] = "ư", [MARK_NONE] = "u"},
["ư"] = {[MARK_HORN] = "ư", [MARK_NONE] = "u"},
["d"] = {[MARK_DASH] = "đ", [MARK_NONE] = "d"},
["đ"] = {[MARK_DASH] = "đ", [MARK_NONE] = "d"},
}
if mapping[char] and mapping[char][mark] then
char = mapping[char][mark]
end
if tone ~= TONE_NONE then
char = add_tone_char(char, tone)
end
return char
end
describe("add_mark_char", function ()
it("adds mark to a char", function ()
assert.are.equal(add_mark_char("e", MARK_HAT), "ê")
assert.are.equal(add_mark_char("e", MARK_NONE), "e")
assert.are.equal(add_mark_char("ụ", MARK_HORN), "ự")
end)
end)
Và hàm hỗ trợ get_tone_char.
function get_tone_char (char)
for i, v in pairs(VOWEL_CHARS) do
if char == v then
return (i - 1) % 6 + 1
end
end
return TONE_NONE
end
describe("get_tone_char", function ()
it("returns the tone", function ()
assert.are.equal(TONE_ACUTE, get_tone_char("á"))
assert.are.equal(TONE_HOOK, get_tone_char("ỷ"))
assert.are.equal(TONE_NONE, get_tone_char("e"))
end)
end)
Công việc đầu tiên cần làm là định nghĩa cấu trúc dữ liệu tốt, thuận tiện cho việc xử lý tiếng Việt.
Kiểm tra chữ cái đầu vào có phải một chữ cái nguyên âm không.
function is_vowel_char (char)
if string.find("aeiouy", char) then
return true
else
return false
end
end
Chương trình của chúng ta sử dụng bộ kiểm thử busted cho Lua. Nếu chạy file Lua thông qua bộ kiểm thử thì nó sẽ định nghĩa sẵn các hàm describe, it,… còn nếu không thì sẽ không có và không thể thực thi chương trình. Vậy chúng ta sẽ định nghĩa một hàm describe giả trong trường hợp chương trình không chạy qua bộ kiểm thử.
if describe == nil then
describe = function () end
end