Home Posts

Вывод кириллицы в консоль на языке Nim

Обновление: с версии Nim 0.15.0 консоль по умолчанию выставляет кодировку stdout в utf-8 (см. issue #4417).

Есть такой замечательный язык — называется Nim, в котором всё хорошо, но есть лишь одна загвоздка: кириллица рисуется кракозябрами в консоли из-под Windows.

Почему так? Сейчас узнаем.


Возьмём простую программу, которая должна вывести слово «привет»:

echo "привет!"

Компилируем, запускаем, и видим:

$ nim c -r app.nim
╨┐╤А╨╕╨▓╨╡╤В!

Да, ошибаться невозможно, это точно они — кракозябры. От нашего сообщения в неизменном виде остался только знак восклицания.

Попробуем разобраться в чём дело — попросим компилятор Nim собрать нам исходники на C:

$ nim c -c app.nim

Теперь в папке nimcache нас ждут файлы stdlib_system.c и app.c. Первый нам не интересен: это стандартная библиотека Nim. Посмотрим в app.c как была сохранена наша строка «привет!». Ищем, после чего находим такое объявление:

STRING_LITERAL(TMP140, "\320\277\321\200\320\270\320\262\320\265\321\202!", 13);

Во что это превратилась наша строка? Я отвечу — это UTF-8. Компилятор Nim преобразовал нашу строчку в UTF-8, и поэтому консоль не может вывести символы правильно, потому что в ней установлена другая кодировка. Обычно под «русской виндой» установлена кодировка windows-1251. Всё, что нам нужно сделать — поменять кодировку консоли на UTF-8.

Лезем в Microsoft Developer Network, откапываем оттуда функции GetConsoleOutputCP и SetConsoleOutputCP, которые и позволят нам сменить кодировку:

when defined(windows):
  proc setConsoleOutputCP(codepage: cint): cint
    {.stdcall, dynlib: "kernel32", importc: "SetConsoleOutputCP".}
  proc getConsoleOutputCP(): cint
    {.stdcall, dynlib: "kernel32", importc: "GetConsoleOutputCP".}

Так как мы с вами не индусы, мы сначала запомним старую кодировку, установим свою, а когда выйдем из программы, то восстановим исходную:

when defined(windows):
  let g_originalOutputCP = getConsoleOutputCP()
  # 65001 - utf-8 codepage
  discard setConsoleOutputCP(65001)

echo "привет!"

when defined(windows):
  # Restore original codepage
  discard setConsoleOutputCP(g_originalOutputCP)

Пробуем вывести нашу строчку "привет!".

$ nim c -r app.nim
привет!

Готово!

А вот и полный код:

when defined(windows):
  proc setConsoleOutputCP(codepage: cint): cint
    {.stdcall, dynlib: "kernel32", importc: "SetConsoleOutputCP".}
  proc getConsoleOutputCP(): cint
    {.stdcall, dynlib: "kernel32", importc: "GetConsoleOutputCP".}

  let g_originalOutputCP = getConsoleOutputCP()
  # 65001 - utf-8 codepage
  discard setConsoleOutputCP(65001)

echo "привет!"

when defined(windows):
  # Restore original codepage
  discard setConsoleOutputCP(g_originalOutputCP)

Чтобы не парится, можно восстанавливать кодовую страницу через addQuitProc.

Вот и всё!