-- @@2023-07-07 release -- LICENSED UNDER CC0 -- stylua: ignore start --[[ js autogen: var a ="АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"; var q = a + a.toLowerCase(); var ret = q.split('').map(L=>`["${L}"]="${L.toLowerCase()}"`).join(", ") console.log(ret) second lcased half is not "strictly" required in this example though; ]] local cheat_sheet_map_RU = { ["А"]="а", ["Б"]="б", ["В"]="в", ["Г"]="г", ["Д"]="д", ["Е"]="е", ["Ё"]="ё", ["Ж"]="ж", ["З"]="з", ["И"]="и", ["Й"]="й", ["К"]="к", ["Л"]="л", ["М"]="м", ["Н"]="н", ["О"]="о", ["П"]="п", ["Р"]="р", ["С"]="с", ["Т"]="т", ["У"]="у", ["Ф"]="ф", ["Х"]="х", ["Ц"]="ц", ["Ч"]="ч", ["Ш"]="ш", ["Щ"]="щ", ["Ъ"]="ъ", ["Ы"]="ы", ["Ь"]="ь", ["Э"]="э", ["Ю"]="ю", ["Я"]="я", ["а"]="а", ["б"]="б", ["в"]="в", ["г"]="г", ["д"]="д", ["е"]="е", ["ё"]="ё", ["ж"]="ж", ["з"]="з", ["и"]="и", ["й"]="й", ["к"]="к", ["л"]="л", ["м"]="м", ["н"]="н", ["о"]="о", ["п"]="п", ["р"]="р", ["с"]="с", ["т"]="т", ["у"]="у", ["ф"]="ф", ["х"]="х", ["ц"]="ц", ["ч"]="ч", ["ш"]="ш", ["щ"]="щ", ["ъ"]="ъ", ["ы"]="ы", ["ь"]="ь", ["э"]="э", ["ю"]="ю", ["я"]="я", } -- stylua: ignore end local s_lower = string.lower local s_sub = string.sub local t_concat = table.concat local function manual_lowercase(s) local ret = {} local ret_len = 0 local idx = 1 local len_s = #s local cheat while idx <= len_s do cheat = cheat_sheet_map_RU[s_sub(s, idx, idx + 1)] if cheat then ret_len = ret_len + 1 ret[ret_len] = cheat idx = idx + 1 -- since we consumed 2 "bytes" else ret_len = ret_len + 1 ret[ret_len] = s_lower(s_sub(s, idx, idx)) end idx = idx + 1 end return t_concat(ret, "") end print(manual_lowercase("ЫХАЛО walks into the bar")) print(manual_lowercase("then 1 ПРЫНЦ Машет Ему Лапкой")) print(manual_lowercase('and lastly, "THE PARTY" beginz!'))