bmstu-iu9 / refal-5-lambda

Компилятор Рефала-5λ
https://bmstu-iu9.github.io/refal-5-lambda
Other
79 stars 35 forks source link

Поддержка Юникода в Рефале-5λ #27

Open Mazdaywik opened 8 years ago

Mazdaywik commented 8 years ago

Текущая реализация Рефала-5λ не поддерживает Юникод, интерпретирует текст как последовательность байт. Поэтому с текстами в кодировке UTF-8 он частично совместим, ровно на столько, на сколько UTF 8 может заменить однобайтную кодировку.

Требуется реализовать поддержку Юникода и кодировок символов на уровне лексики (в частности, национальные символы в идентификаторах), библиотеки времени выполнения и библиотеки стандартных функций.

Библиотеку для Юникода можно реализовывать тремя путями:

  1. по принципу чучхэ¹ — самим реализовать всё на C++,
  2. используя API операционной системы, при этом должны поддерживаться как минимум три ОС: Windows, Linux и OS X (есть подозрение, что у Linux и OS X API будет разным);
  3. используя библиотеками <locale> и <wctype.h>, но насколько точно и переносимо они поддерживают Юникод в разных версиях компиляторов — точно не знаю.

У каждого варианта свои достоинства и недостатки, нужно будет выбрать какой-то один и его реализовать.


¹ Чучхэ (кор.) — идеология опоры на собственные силы

ldinc commented 8 years ago

https://github.com/nemtrif/utfcpp

Mazdaywik commented 7 years ago

Роберт Тебиев (@tebrobert), работавший над этой задачей, вроде как ушёл из вуза. Поэтому задача переходит на следующий осенний семестр.

Mazdaywik commented 7 years ago

Забираю задачу себе, поскольку в качестве курсового её никто не взял.

Mazdaywik commented 6 years ago

Сначала, вроде как Роберт (@tebrobert) восстановился, приходил на консультацию, а сегодня я узнал, что он снова ушёл. Задачу исключаю из вехи.

Mazdaywik commented 6 years ago

Задача актуальна для курсового проекта.

Mazdaywik commented 5 years ago

Никто не взял как тему курсового.

Mazdaywik commented 5 years ago

Задача снова вывешивается на курсовую работу.

Кстати, на момент написания комментария — это самая старая задача из открытых.

Mazdaywik commented 5 years ago

Задача не была выбрана в качестве курсовой.

Mazdaywik commented 4 years ago

Не буду предлагать на курсовую, т.к. задача подразумевает изменение рантайма, объём которых мне сейчас не очевиден.

tonal commented 3 years ago

Мне кажется, задачу нужно разбить на несколько подзадач, чтобы с ней можно было как-то работать. Примерно так:

  1. Выбор внутреннего представления.
    • Например, в Haskell, Python, Java, Qt используется UTF-32 - очевидные плюсы - в большинстве случаях символ имеет фиксированный размер - соответственно скорость обработки практически не отличима от обычных char. Очевидные минусы - большой перерасход памяти в стандартных случаях. Кроме того в патологических случаях символ не вмещается в 32бита. Правда для идентификаторов можно специально ограничить набор символов - т. е. на скорость рантайм это не должно повлиять.
    • UTF-16 - используется в Win32. Промежуточный по памяти вариант. Но если вспомнить про Китайцев - то от многопозиционных символов не уйти.
    • UTF-8 - скорость будет всегда в пролёте. Памяти - минимум.
  2. Что-то нужно решать с байтовыми данными. Как-то разграничить работу и применение просто буферов байтов, C-сторк и символов. В приведённых выше языках это решается на уровне системы типов.
  3. Далее, из первых 2х пунктов выбирать реализацию собственно работы с unicode.
  4. Добавить выбранную библиотеку в рантайм.
  5. Добавить в компилятор и научить его работать с литералами и идентификаторами в unicode.
  6. Что-то решить с линкером и вставками на C/C++.
Mazdaywik commented 3 years ago

Добрый день, @tonal!

  1. Выбор внутреннего представления — UTF-32. Данные в памяти представляются как двусвязные списки звеньев с тегом и полем информации: https://github.com/bmstu-iu9/refal-5-lambda/blob/21d12cc4071e73f4297f0ba958c31b7e3a54aff6/src/lib/refalrts.h#L114-L134 Так что, если заменить char на int, никакого перерасхода памяти не будет.

    Для представления имён функций и символов-слов будет использоваться UTF-8 ради компактности и удобства работы.

  2. Байтовые данные на данный момент просто обрабатываются как строки из чисел 0…255. Скорее всего, потребуется написать функции, преобразующие строки char’ов в строки чисел 0…255 в той или иной кодировке. Это вопрос исключительно библиотеки, а не рантайма.
  3. Тут нужно не только сам компилятор (лексер) научить читать литералы, идентификаторы и т.д., но и научиться читать файлы в различных кодировках (UTF-8, UTF-16 оба вида, UTF-32 оба вида, однобайтовые вроде 1251…).
  4. Нативные вставки я планирую удалить (https://github.com/bmstu-iu9/refal-5-lambda/issues/318#native). Сложности с C/C++ я пока не вижу. Для имён функций в Unicode нужно будет расширить декорирование имён, но это технический вопрос.
tonal commented 3 years ago

Хорошего окончания года @Mazdaywik!

  1. Тут есть чуток ремарок: а) Некоторые символы могут иметь представление как 2 и более кода. Самые очевидные Й и Ё - они могут быть представлены 1 собственно символом, или 2мя - основным и надстрочным совмещённым. б) Хуже того. Сортировка может зависеть от несколькобуквенных последовательностей - возникает неоднозначность при сопоставлениях с образцом... в) В том же Haskell-е именно такое представление и было изначально. Но оказалось, что для реальной работы оно изрядно тормозное. Поэтому сейчас используются пакеты ByteString при любой сколько-нибудь массовой работы со строками (в компиляторе так же).

  2. См. П 5 и П 1.б.

  3. (п5) В том же Python-е стандартизировали комментарий в начале файла, в котором можно указать кодировку. Если не указано - считается utf-8. В С++ добавили флаг компиляции, позволяющий указать кодировку исходника. В Haskell - зафиксировали utf-8. Но, в любом случае компилятор должен быть осведомлён о кодировке, и использовать какие-то библиотеки для конвертации. Предоставлять ли их как доступные в рантайм - вопрос открытый. Но у компилятора они должны быть.

Mazdaywik commented 3 years ago

@tonal, спасибо за интересные дополнения, в частности, за опыт с другими языками программирования. С Хаскелем я не работал, поэтому этих тонкостей не знал. С Python работал, в глубины особенностей реализации Юникода и кодировок не углублялся. Знаю только про комментарий-кодировку (правда, не знаю, как Python работает с файлами в кодировках UTF-16 и UTF-32 без BOM).

Флаг компилятора в C++ с кодировкой исходника — это, по-видимому, особенность той реализации C++, которой Вы пользуетесь. Странно было бы, если бы эту функцию затребовали в Стандарте. Вообще C++11 и последующие уже поддерживают строковые литералы в UTF-16 и UTF-32, поэтому как-то работать с этим надо.

По поводу того, как хранить составные символы (ё, й, ñ, ударе́ние…) в памяти — как они загрузились из файла, так и хранить. Но при этом иметь библиотечные функции для приведения текста к той или иной нормальной форме (там их 4). Потому что учитывать эти составные символы в процессе сопоставления с образцом — жутко сложно и накладно.

Кстати, на macOS почему-то «ё» и «й» по умолчанию представляются парой кодовых точек. Иногда мне присылают картинки, созданные на macOS, в именах которых находятся упомянутые буквы. IrfanView открыть их не может.

Кстати, знаки-модификаторы сейчас используются не только для точек над «ё». Эмодзям можно задать цвет кожи и пол, которые тоже задаются модификаторами. Без модификатора цвета кожи смайлик показывается жёлтым, с модификатором — от розового до чёрного. Без модификатора пола — какой-то пол по умолчанию, у разных смайликов разный. Сейчас я пишу с компьютера с Windows 7, на нём геморно смайлики вводить. Сяду за компьютер с Windows 10 — накидаю примеров в комментарии, если не забуду.

А вообще, хороший вопрос, как правильно компилировать файлы с разными кодировками.

В самом языке имена функций и переменных сейчас могут содержать только латинские буквы ASCII с учётом регистра символа, поэтому кодировка на них не влияет. Влияет кодировка на константы — литеры (characters) и символы-слова в кавычках (что-то вроде цитированных имён в Лиспе). Сейчас они просто рассматриваются как байты. Если исходник в кодировке UTF-8, то русская буква в памяти будет храниться как два отдельных character’а, какой-нибудь иероглиф — три или четыре.

Но если поддерживать Юникод, то нужно предложить разумное поведение для случая, когда кодировка исходника не определена. Например, можно так: если определена правильно (есть псевдокомментарий, или начинается на BOM и при считывании не было ошибок), то компилируется тихо-спокойно. Если не определена, то на символы с кодом больше 127 (кроме комментариев) можно выдавать предупреждение или даже ошибку, что это какая-то фигня.

Хороший вопрос с нормализацией и символами-словами с составными знаками (вроде тех же «ё»). Исходники могут совместно правиться под разными системами, и один и тот же по начертанию и написанию символ будет представлен в файле по-разному. Но он при этом во время выполнения должен быть одним и тем же. Возможное решение — для символов-слов принудительно применять нормализацию. Для символов-литер это менее актуально, но тоже можно применять некую нормальную форму (какую из четырёх?).


Сейчас из поддержки Юникода компилятор умеет только игнорировать UTF-8 BOM в начале — не выдаёт на него ошибку.