Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Новости и прочее

Выбор кодировки для компилятора

Чтобы язык программирования, давал возможность программировать на родном языке, его компилятор должен использовать кодировку, которая бы обеспечивала удобство работы с многими языками. Идеальных кодировок для этого нет. Использовать 8-битные кодировки слишком непрактично по многим причинам. Старые кодировки типа ASCII неудобны для поддержки нескольких языков.

кодировка для компилятора

        Прежде, чем остановиться на какой либо кодировке, давайте сформулируем, каким требованиям она должна отвечать.

  1. Она должна поддерживать все или почти все распространённые языки.
  2. Символы этой кодировки должны иметь постоянный размер (в байтах, конечно, а не в пикселах).
  3. Желательно, чтобы символы в выбранной кодировке были бы представлены в ней единственным способом.
  4. Массив, индексом которого выступает код символа, должен помещаться в оперативной памяти.
        Прежде, чем обсудить эти требования и способы их воплощения, рассмотрим некоторые примеры. Когда происходит лексический разбор, то лексему «идентификатор» в учебниках разбирают примерно так: (это первое приходит на ум, да и в учебниках пишут именно так):
bool  Это идентификатор (char  входная строка) {
    char  символ = входная строка [0];
    if (! ( 'A' <= символ && символ <= 'Z' || 'a' <= символ && символ <= 'z'))
        return  false;
    for (int i=1; i < strlen (входная строка); i++) {
        символ = входная строка [i];
        if (! ( 'A' <= символ && символ <= 'Z' || 'a' <= символ && символ <= 'z'
             || '0' <= символ && символ <= '9' ))
            return  false;
    }
    Поместить идентификатор в таблицу(....);
    return  true;
}
        Если в язык добавляем символы русского алфавита, то разбор уже такой:
bool  Это идентификатор (char  входная строка) {
   char  символ = входная строка [0];
   // добавляем проверку символов кириллицы
   if (! ('A' <= символ && символ <= 'Z' || 'a' <= символ && символ <= 'z' ||
          'A' <= символ && символ <= 'Я' || 'a' <= символ && символ <= 'я' ||
           символ == 'Ё' || символ == 'ё'))
      return  false;
   for (int i=1; i < strlen (входная строка); i++) {
      символ = входная строка [i];
      // добавляем проверку символов кириллицы
      if (! ('A' <= символ && символ <= 'Z' || 'a' <= символ && символ <= 'z' ||
             'A' <= символ && символ <= 'Я' || 'a' <= символ && символ <= 'я' || 
             символ == 'Ё' || символ == 'ё' ||
             '0' <= символ && символ <= '9' ))
          return  false;
   }
   Поместить идентификатор в таблицу(....);
   return  true;
}
        Если язык должен поддерживать греческий, грузинский, армянский, арабский алфавиты, то код распухает ещё больше. Но можно использовать приём, который приведёт к значительному упрощению программы. Массив, описанный ниже, содержит некоторые признаки для каждого символа. Для получения признаков символа с кодом X нужно прочитать элемент массива с индексом X:
массив признаков [код символа A] = ПЕРВЫЙ СИМВОЛ ИДЕНТИФИКАТОРА |
                                   СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА |
                                   ШЕСТНАДЦАТИРИЧНАЯ ЦИФРА ... ;
...
массив признаков [код символа F] = ПЕРВЫЙ СИМВОЛ ИДЕНТИФИКАТОРА |
                                   СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА |
                                   ШЕСТНАДЦАТИРИЧНАЯ ЦИФРА ... ;
массив признаков [код символа G] = ПЕРВЫЙ СИМВОЛ ИДЕНТИФИКАТОРА |
                                   СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА ... ;
...
массив признаков [код символа Z] = ПЕРВЫЙ СИМВОЛ ИДЕНТИФИКАТОРА |
                                   СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА ... ;
массив признаков [код символа 0] = СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА |
                                   ЦИФРА | 
                                   ШЕСТНАДЦАТИРИЧНАЯ ЦИФРА ...;
...
массив признаков [код символа 9] = СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА |
                                   ЦИФРА | 
                                   ШЕСТНАДЦАТИРИЧНАЯ ЦИФРА ...;
        А дальше разбор лексемы "идентификатор" выглядит так:
bool  Это идентификатор (char  входная строка) {
    char  символ = входная строка [0];
    if (! (массив признаков [символ] & ПЕРВЫЙ СИМВОЛ ИДЕНТИФИКАТОРА ))
        return  false;
    for (int i=1; i < strlen (входная строка); i++) {
        символ = входная строка [i];
        if (! (массив признаков [символ] & СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА ))
            return  false;
    }
    Поместить идентификатор в таблицу(....);
    return  true;
}
        После этого код становится проще даже в сравнении с первым вариантом, который только для латиницы. Уменьшается число сравнений, что увеличивает скорость разбора. Но! Вышеприведённый массив должен иметь в качестве индекса код символа. Вот только в какой кодировке должен быть этот символ? Конечно же, в первую очередь думаем о Юникоде.

Справка. Первая версия Юникода представляла собой кодировку с фиксированным размером символа в 16 бит. Но вследствие расширения кодового пространства Юникод сам по себе перестал является кодировкой. Для кодирования символов Юникода стали использовать UTF (англ. Unicode Transformation Format): UTF-8, UTF-16, UTF-32.

Все версии Юникода позволяют использовать 1 112 064 символа. Это число было выбрано в качестве окончательной величины кодового пространства Юникода. Нулевая (базовая) плоскость Юникода (первые 65536 символов), позволяет использовать алфавиты практически всех языков. За переделами нулевой плоскости — редко используемые иероглифы и исторические письменности.

Некоторые символы Юникода могут быть представлены не одним способом. Например, символ «Й» представляется 2 способами:
— (U+0419)
— в виде базового символа «И» (U+0418) и модифицирующего символа «крючок над буквой» (U+0306)

UTF-8: символы в этой кодировке имеют переменную длину и занимают от 1 до 6 байтов — это в теории. На практике от 1 до 4 байтов, поскольку байты 5 и 6 не используются.

UTF-16: символы в этой кодировке имеют переменную длину и имеют размер 2 или 4 байта.

UTF-32: символы в этой кодировке занимают ровно 32 бита.

        Рассмотрим сразу пункт II наших требований, поскольку пункт I в комментариях пока не нуждается. Использую кодировки, в которых символы кодируются переменным числом байтов, мы лишаемся возможности обращаться к строкам, используя порядковый номер символа: string[i]. Чтобы узнать количество символов в строке, нужно провести её разбор. А уж если строку читать с конца, то возникают трудности с определением предыдущего символа.

        Но это не всё. Как мы видим, что некоторые символы могут кодироваться более, чем одним способом, типа буквы «Й» (см. пункт III). Чем это мешает? Тем, что одни и те же идентификаторы могут быть представлены несколькими способами. Поэтому нам нужно заменить составные символы на обычные, занимающие столько же битов, что и все. Википедия описывает формы нормализации для таких случаев, нужно использовать форму нормализации NFC. Эта тема требует дальнейшей проработки, чтобы выявить возможные подводные камни.

        Теперь пункт IV. Нам нужно обеспечить, чтобы массив характеристик для каждого символа помещался в память. И чем меньше он будет, тем лучше. Более миллиона символов (1 112 064) — это тоже многовато. Здравый смысл подсказывает, что шумерская письменность в языках программирования не нужна, древние шумеры не восстанут из небытия и не потребуют равенства возможностей. Выбирая нулевую (базовую) плоскость Юникода (первые 65536 символов), мы оставим за бортом лишь редко используемые иероглифы. Остальные символы оставшихся 16 плоскостей Юникода не имеют практической пользы для языка программирования.

        Поэтому из всех кодировок останавливаемся на UTF-16. 65536 символов нас устроят. Можно вспомнить ещё такой факт, что диапазон D80016..DFFF16 используется для кодирования так называемых суррогатных пар — символов, которые кодируются двумя 16-битными словами. Поскольку наша цель — 16 бит на символ, то символы этого диапазона нужно вычеркнуть. Итого: 65536-2048=63488

Как быть с другими кодировками?

        Мы вели речь о кодировке для компилятора. Т.е. «внутри» компилятора, в служебных таблицах и словарях должна использоваться UTF-16. Но это не значит, что «снаружи» не может быть подан текст в других кодировках: ASCII, UTF-8 и других. Среда разработки для нашего языка должна работать с UTF-16 нулевой плоскости — так удобнее. Но эта среда должна преобразовывать исходный текст из любых распространённых кодировок в выбранную нами. В случае неиспользования IDE компилятор так должен уметь выполнять такие же преобразования.

Выводы

  • Первые 65536 символов кодировки UTF-16 (нулевая плоскость Юникода) обеспечат возможностью программирования на родном языке. Эти символы имеют фиксированные размер. Массив, описывающий характеристики символов, будет иметь приемлемый размер.
  • Необходимо избавляться от составных символов, все символы должны иметь размер 16 битов
  • Остальные кодировки должны преобразовываться к выбранной нами UTF-16.

Опубликовано: 2013.03.24, последняя правка: 2021.01.18    13:25

ОценитеОценки посетителей
   ██████████████████ 3 (42.8%)
   ██████████████████ 3 (42.8%)
   ██████ 1 (14.2%)
   ▌ 0

Отзывы

     2016/01/03 11:31, Павиа          # 

Нет кодировки лучше GB18030 ;-)

     2016/01/03 14:57, Автор сайта          # 

Смех смехом, но кодировку выбирать всё равно придётся.

В этой статье, изложено, почему UTF-16 — лучшее решение. Но потом подумалось, что на 64-разрядных системах нет 16-битных регистров и команд. Работать с 16-битными объектами, конечно, можно, но с дополнительными накладными расходами. Пришёл к выводу, что 32-битнная кодировка хоть и избыточна, но легче ложится на х86-64. И вот попадается информация, что в библиотеке Qt класс для работы со строками QString оперирует со строками в UTF-16. И тут у меня лёгкий ступор. А как они работают на х86-64? Вроде как в этой архитектуре нет 16-разрядных регистров и нельзя обратиться в память и прочитать ровно 16 бит. Или я что-то не догоняю, всё-таки в х86-64 можно работать напрямую с 16-битными объектами? Или в Qt пошли на небольшие извращения и таки запихнули плохо впихуемое?

     2016/01/05 18:26, Павиа          # 

QString имеют универсальный интерфейс подходящий под любую Юникодовую кодировку. Но на нижнем уровне там 8-битные строки. Но прикрутить 16-бит не составляет труда, хедеры в сети видел.

По поводу избыточности. 80% информации на жестком диске составляет текстовая не сжатая информация! Это справка, XML файлы, txt, макросов и коды программ.
А текстовая информация сжимается в 10 раз. Т.е. диск расходуется впустую.

Нет смысла делать 32 бита. Индексный доступ нужно только для обучения программированию. А на практике основная операция со строками это копирования, а также конкатенация(тоже копирование). И чем строка короче тем быстрее идёт работа.

Во-вторых 32 бита нужны только для КЯК. Но если посмотреть на Юникод. То там 30 тысяч иероглифов расположено в приделах 16 бит. И только 8 тысяч выше 16 бит. Хватило бы 17 бит.

Собственно поэтому 50% сайтов в Китае на gb2312 16-битной кодировке, которая имеет покрытие 99.75%. Им хватает! И не надо делать больше. Кому не хватает, тот пользуется gb18030.

И вам советую посмотреть gb18030, так как только Китай поддерживает интересы России и составил правильную кодировку. В отличие от американцев и их подопечных, которые специально суют палки в колеса. Вернее, кодировки портят намерено.

     2016/01/08 16:27, Автор сайта          # 

Я прочитал про UTF-16 в QString, а Вы пишите про 8-битные строки. Т.е. Вы опровергаете?

Но прикрутить 16-бит не составляет труда

Да это элементарно сделать самому. Просто лексический разбор будет чуть медленнее, если 16-битные данные обрабатывать 8 или 32-битными командами.

Индексный доступ нужно только для обучения программированию

Да нет же, выше написано, как сделать очень простой и очень быстрый лексический анализатор, применив для этого индекс, который совпадает с кодом символа. Можно было бы сделать свою собственную кодировку с таким расположением букв: АаБб..ЕеЁёЖж..Яя. Вы не находите её логичной? Буква «а» в ней стоит впереди «Б», а в UTF, cp-2151, cp-866 — наоборот. Буквы «Ёё» стоят на месте. В принципе, почему бы нет? Ведь это внутренняя для компилятора кодировка! Но её минус в том, что её надо разрабатывать для каждого языка, который планируется использовать в языке программирования. Большинство языков обойдётся 8-битной кодировкой. Правда, союзникам 8 бит будет мало.

     2016/04/03 16:28, Вежливый Лис          # 

С приходом UTF-8 исчезло понятие "текстовый редактор". Остались только текстовые процессоры. Ибо как можно что-то редактировать по одному симоволу, если в Unicode есть накладывающиеся последовательности (например и + крышечка = й ). Такая последовательность — это два кода unicode, но выглядит на экране как одно знакоместо. Значит это уже не текстовый редактор, а WYSIWYG-текстовый процессор.

     2016/04/03 16:44, Автор сайта          # 

Нет, не исчезло. Википедия:

Notepad++ — свободный текстовый редактор с открытым исходным кодом для Windows...

А он работает и с UTF-8, и с cp-1251. Давайте не будем ввязываться в терминологический спор. Но работать с кодировкой, где символы однозначно представлены словом фиксированной разрядности, гораздо удобнее.

     2016/04/15 13:19, utkin          # 

Люди уже предложили годное решение — выбрать готовую библиотеку для работы с нужной кодировкой и не заморачиваться. Чуть быстрей, чуть медленней для 4-хядерного процессора сейчас вообще не актуально. Большую часть времени процессор простаивает ожидая реакции юзера. Поэтому смысла в том, что компилятор будет работать на 0,0007 микросекунды быстрей особого нету. Оптимизация будет нужна только в случае реальных проблем со скоростью. Ну вон та же Ява далеко не спринтер, но лидер и решает большое количество задач разного направления.

     2016/04/15 15:29, Автор сайта          # 

Не знаю ни одного человека, кто бы протестовал против быстрой работы его программ. Если можно сделать быстро — почему бы и нет? Тем более, что для этого всё есть.

     2016/04/18 07:53, utkin          # 

Это да, но! Полная скорость включает время на создание инструмента его автором. То есть то время, которое будет потрачено на создание компилятора. Если из-за библиотеки его разработка затянется на несколько лет, компилятор может вообще стать не нужным. То есть если я компилирую прогу сейчас и жду две секунды больше или я компилирую мгновенно через 3 года, то разница для меня очевидна. Вот этого рядовые программисты никак не могут принять. С одной стороны действительно — медленный и неэффективный код заставляет людей покупать более мощное и более дорогое железо. С другой стороны время и деньги в конечном счете на стороне неэффективных программ работающих сейчас, а не оптимизированных систем работающих потом.

     2016/04/18 11:59, Автор сайта          # 

Ваши рассуждения логичны. Но написать функции преобразования из одной кодировки в другую — дело, измеряемое часами, а не годами. Если б проблемы при создании языка и написании компилятора упирались только в преобразование кодировок, был бы просто счастлив.

     2017/05/03 03:17, rst256          # 

Да нет же, выше написано, как сделать очень простой и очень быстрый лексический анализатор, применив для этого индекс, который совпадает с кодом символа. Можно было бы сделать свою собственную кодировку с таким расположением букв: АаБб..ЕеЁёЖж..Яя. Вы не находите её логичной? Буква «а» в ней стоит впереди «Б», а в UTF, cp-2151, cp-866 — наоборот. Буквы «Ёё» стоят на месте. В принципе, почему бы нет? Ведь это внутренняя для компилятора кодировка! Но её минус в том, что её надо разрабатывать для каждого языка, который планируется использовать в языке программирования. Большинство языков обойдётся 8-битной кодировкой. Правда, союзникам 8 бит будет мало.

С чего бы это дополнительный проход при компиляции, для создания этой самой внутренней для компилятора кодировки, сделает лексический анализ более быстрым?
А необходимость обратного преобразования в исходную кодировку тех же строковых литералов добавит простоты?
Ну а насчет расположения букв "АаБб..ЕеЁёЖж..Яя" для внутренней кодировки в чем смысл?

     2017/05/18 11:29, Автор сайта          # 

дополнительный проход

Зачем дополнительный проход? Хватит простой таблички для перекодировки.

необходимость обратного преобразования в исходную кодировку

Обратное преобразование необходимо при выводе информации. Если не надо выводить, то и преобразовывать не надо.

насчет расположения букв "АаБб..ЕеЁёЖж..Яя" для внутренней кодировки в чем смысл?

Просто сама такая кодировка нравится, безо всякой связи с компиляторами. Сортировка в такой кодировке удобнее.

     2017/10/18 13:14, rst256          # 

Просто сама такая кодировка нравится, безо всякой связи с компиляторами. Сортировка в такой кодировке удобнее.

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

     2017/10/25 13:20, Автор сайта          # 

Достаточно один раз возникнуть такой необходимости — и всё, надо делать. Нужна проверка существования некого типа или переменной? Значит надо сделать поиск среди ранее определённых сущностей. А где поиск — там и сортировка, это очень связанные задачи. Недаром Дональд Кнут объединил эти темы в один том.

     2019/03/20 17:54, rst256          # 

А где поиск — там и сортировка, это очень связанные задачи.

Да, только для поиска всё равно, какая там кодировка.

Достаточно один раз возникнуть такой необходимости — и всё, надо делать.

Да, но ведь можно организовать такую сортировку, не вводя нестандартную кодировку. Например, приводя буквы к верхнему регистру перед сравнением или задав свой порядок сортировки массивом. Гораздо проще написать функцию сортировки для строк в Юникоде, чем каждый раз перекодировать текст, когда надо вывести его в консоль из файла. И при разборе, наверное, придётся учитывать, что строковые константы не нужно переводить во внутреннюю кодировку, и с дампом памяти сложно будет работать.

     2019/03/21 16:51, Автор сайта          # 

Если язык регистрозависимый, то приводить символы к одному регистру не надо. Но при сравнении надо сравнивать не сами символы, а их порядковые номера в алфавите (функция ord() во многих языках). Символы «Ё» и «ё» вне основного массива — для любых принятых сейчас кодировок. Без ord() из-за «Ё» и «ё» сравнение отработает неправильно.

     2019/03/28 02:58, rst256          # 

Символы «Ё» и «ё» вне основного массива — для любых принятых сейчас кодировок.

Это лишь немного усложнит код для определения порядка сортировки, что, несомненно, понизит скорость сортировки. Но так ведь и применять такую сортировку нам в компиляторе вряд ли придётся, для поиска в таблицах символов порядок сортировки роли не играет.

     2019/03/28 22:53, Автор сайта          # 

И вправду, если не быть дотошным, не страдать манией совершенства, то на небольшое нарушение порядка можно закрыть глаза. Встречал коммерческое ПО, в котором «ё» — не между «е» и «ж».

     2019/04/01 15:31, rst256          # 

И вправду, если не быть дотошным, не страдать манией совершенства, то на небольшое нарушение порядка можно закрыть глаза. Встречал коммерческое ПО, в котором «ё» — не между «е» и «ж».

Нет мы при желании всегда можем исправить это небольшое нарушение порядка, не меняя при этом кодировку! Мыслите абстрактнее, порядок сортировки не должен зависеть от кодировки символов, порядок сортировки зависит от конкретной функции сравнения! И пусть мы никогда не получим символ "ё" в результате операций ("е"+1) и ("ж"-1), мы тем не менее можем иметь сортировку, в которой «ё» будет между «е» и «ж» просто, написав для нее соответствующую функцию сравнения:
//возвращает номер символа по порядку для русского алфавита или 0
целое порядок_символа_в_русском_алфавите(символ симв)
если симв>="а" и симв<="е" тогда возврат "а"-симв
иначеесли симв>="ж" и симв<="я" тогда возврат "ж"-симв+1
иначеесли симв=="ё" или симв=="Ё" тогда возврат "ж"+1
//ветки для загл. букв опущены
иначе возврат 0 //если это не символ русск. алфавита возвращаем 0
конец
конец

целое сравнение_символов_русского_алфавита(символ а, символ б)
целое пор_а = порядок_символа_в_русском_алфавите(а)
целое пор_б = порядок_символа_в_русском_алфавите(б)
если пор_а==0 и пор_б!=0 тогда возврат -1 конец
если пор_а!=0 и пор_б==0 тогда возврат 1 конец
//нерусские символы будут идти после русских
если пор_а==0 и пор_б==0 тогда возврат а-б конец
//сортировка для нерусских символов производится по их коду
возврат пор_а-пор_б
//сортировка для русских символов производится по алфавиту см. порядок_символа_в_русском_алфавите
конец
Ещё замечу что под описание "сортировка в алфавитном порядке (для русского языка)" подходят довольно много вариантов. Сортировать независимо от регистра или нет? А если нет, то в каком порядке? Сперва заглавные или наоборот? В каком порядке располагать символы не из русского алфавита? До или после? Или же вообще по-особому (например цифры перед русскими, а остальные после)? И т.д. и т.п.

И, кстати, независящую от регистра сортировку по алфавиту в принципе невозможно реализовать кодировкой (даже такой как "АаБб..ЕеЁёЖж..Яя"), ведь хоть "а" и "А" расположены рядом друг с другом. Значит, они не равны. И список слов "Арбузов", "арбуз", "Арбузова" должен был быть отсортирован так:
1. арбуз
2. Арбузов
3. Арбузова
Однако, т.к. порядок задается кодировкой, где "а" идет после "А", он будет таким:
1. Арбузов
2. Арбузова
3. арбуз
Так что использование кодировки "АаБб..ЕеЁёЖж..Яя" для сортировки по алфавиту может привести к неправильным результатам.

     2019/04/02 12:25, Автор сайта          # 

Ну да, Вы правы. Компьютеру безразлично, каким образом отсортированы строки. Я механически перенёс то, что должно открываться глазам человека (с его привычками!), на то, что нужно видеть программе.

     2021/01/17 00:00, alextretyak          # 

Если в язык добавляем поддержку кириллицы, то разбор уже такой:

Тут наверное ошибка — имеется в виду «добавляем поддержку русского алфавита», а не кириллицы. Причём «русского алфавита без буквы ё».
    if (! (массив признаков [символ] && ПЕРВЫЙ СИМВОЛ ИДЕНТИФИКАТОРА ))
...
if (! (массив признаков [символ] && СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА ))
Как я предполагаю ПЕРВЫЙ СИМВОЛ ИДЕНТИФИКАТОРА это константа 1, а СЛЕДУЮЩИЙ СИМВОЛ ИДЕНТИФИКАТОРА — это константа 2.
Но в таком случае следует использовать поразрядное И (&), а не логическое (&&).

Признаю целесообразность ограничиться только поддержкой базовой плоскости Юникода (первые 65536 символов), тогда реализация в виде простого массива из 65536 элементов почти оптимальна. Но если поразмышлять на этот счёт...

Если посмотреть в документацию MSVC, то там приводятся конкретные диапазоны символов Юникода, которые можно использовать в идентификаторах. Наибольший код допустимого в идентификаторах символа составляет EFFFD, что в десятичной системе — 983037. Таким образом, потребуется массив из 983038 элементов. Но. А что, если признаки символов хранить не в одном массиве, а в разных — по одному массиву на каждый признак. И если вместо массива использовать hash-set\хэш-множество. Я написал простенькую программу, которая суммирует все диапазоны из документации MSVC и, таким образом, считает сколько символов допустимо использовать в идентификаторах в MSVC. Получилось 971380. Теперь, вместо того, чтобы хранить символы, которые допустимо использовать в идентификаторах, можно хранить только недопустимые, ведь их всего лишь 11658 (983038 — 971380)!

     2021/01/18 00:00, alextretyak          # 

Дабы закрыть эту тему решил провести небольшое исследование.

Как я уже писал в предыдущем сообщении, в Microsoft Visual C++ используется определённый набор диапазонов кодов символов, которые разрешено использовать в идентификаторах. Оказывается, эти диапазоны прописаны в стандарте C++ (в Annex E), и они также используются в языке Swift.Однако, откуда взялись эти диапазоны остаётся загадкой.

В языке C# уже лучше. В том смысле, что в его спецификации не зашиты непосредственно коды символов, а указаны категории символов Юникода (а именно: ‘Lu’, ‘Ll’, ‘Lt’, ‘Lm’, ‘Lo’ и ‘Nl’), которые можно использовать в идентификаторах. (Категории всех символов Юникода указаны в файле UnicodeData.txt в 3-й колонке. Аналогично, в языке Go используются категории ‘Lu’, ‘Ll’, ‘Lt’, ‘Lm’ и ‘Lo’.

В Python ещё лучше — всё определение идентификатора помещается в одной строчке:
The identifier syntax is <XID_Start> <XID_Continue>*
Где <XID_Start> и <XID_Continue> — категории символов для идентификаторов, которые [категории] определены прямо в стандарте Unicode! (конкретно: в файле DerivedCoreProperties.txt. В JavaScript аналогично используются категории ID_Start и ID_Continue.

Но, как говорил В.И.Л.: «Мы пойдём другим путём!» Ну не хочется мне включать коды символов или их диапазоны в исходный текст лексического анализатора без крайней необходимости. И я решил разобраться с теми средствами, которые предоставляют языки Python и C++ и на которых написан или в которые транслируется лексический анализатор языка 11l.

В Python есть метод `isalpha()`, который возвращает `True` для всех символов, принадлежащих одной из категорий ‘Lm’, ‘Lt’, ‘Lu’, ‘Ll’ или ‘Lo’. Что почти в точности совпадает с разрешёнными символами в C# (не считая редко используемую категорию ‘Nl’) и совпадает с разрешёнными символами в Go. Поэтому в Python-коде анализатора можно спокойно использовать `isalpha()`. Ещё в Python есть встроенный модуль `unicodedata`, функция `unicodedata.category()` которого возвращает категорию заданного символа, но считаю, что можно обойтись `isalpha()`. В C/C++ есть функция `iswalpha()`, которая возвращает «не пойми что», причём ещё и в зависимости от текущей локали.

В попытке разобраться с этой функцией я составил такую табличку:
                                    ┌──────────┬────────┐
│ макс.код │ кол-во │
┌───────────────────────────────────┼──────────┼────────┤
│ MSVC identifiers │ 983037 │ 971380 │
│ isalpha() in Python │ 195101 │ 125419 │
│ Unicode 13.0.0 Letter │ 195101 │ 125419 │
│ iswalpha() in MSVC (any locale) │ 1114076 │ 811189 │
│ iswalpha() in GCC (no setlocale) │ 122 │ 52 │
│ iswalpha() in GCC (setlocale "C") │ 122 │ 52 │
│ iswalpha() in GCC (setlocale "" ) │ 195101 │ 94318 │
└───────────────────────────────────┴──────────┴────────┘
Какой вывод/итог?... А решайте сами. Я пока остановился на варианте для Python — `isalpha()`, для C++ — `iswalpha()` с предварительным вызовом `setlocale( LC_CTYPE, "" );`. Теперь лексические анализаторы транспайлеров Python → 11l и 11l → C++ поддерживают кириллицу в идентификаторах, а это собственно то, ради чего это исследование и проводилось.

     2021/01/19 23:24, Автор сайта          # 

имеется в виду «добавляем поддержку русского алфавита», а не кириллицы. Причём «русского алфавита без буквы ё»

в таком случае следует использовать поразрядное И (&), а не логическое (&&)

В обоих случаях Вы правы. Во втором случае код писал по памяти, а не брал готовый из программ. Если б такое было в программе, то быстро бы вылезло.

Признаю целесообразность ограничиться только поддержкой базовой плоскости Юникода (первые 65536 символов)

Вот только огорчает тот факт, что в 64-разрядной архитектуре x86 не нашлось места 16-разрядным регистрам. В x86 сишный тип short 16-разрядный, а в x86-64 — 32-разрядный. Это плохо для 16-битных данных.

можно хранить только недопустимые, ведь их всего лишь 11658 (983038 — 971380)!

Они недопустимы только для идентификаторов. В приведённом массиве признаков планирую разместить биты для определения принадлежности к различного вида лексемам. Например, элемент массива с индексом, равном коду символа «0», будет иметь установленные в «1» биты: «следующий символ идентификатора», «двоичная цифра», «десятичная цифра», «шестнадцатеричная цифра». Для символа «A» — «первый символ идентификатора», «следующий символ идентификатора», «шестнадцатеричная цифра». Последнее зависит от загруженного алфавита. Потому что хочется сделать «Многоязыковое программирование».

Массив признаков позволит заменить функцию «is<класс символа>(символ)» на макрос, который будет определён так:
#define <имя_макроса(символ)> массив признаков [символ] & <признак>
. Это две машинные операции: чтение элемента массива по индексу и логическая операция «и».

возвращает `True` для всех символов, принадлежащих одной из категорий ‘Lm’, ‘Lt’, ‘Lu’, ‘Ll’ или ‘Lo’.

Возвращает для всех букв всех алфавитов разом. Описанный мной приём позволяет возвращать «истину» для строго определённых алфавитов. Потому что (а) алфавиты будут подгружаться при смене языка (см. «Многоязыковое программирование»), (б) совмещение в идентификаторах символов разных алфавитов потенциально таит в себе опасности (см. раздел «Безопасность» статьи «Русский язык и программирование»).

     2021/01/21 00:00, alextretyak          # 

Вот только огорчает тот факт, что в 64-разрядной архитектуре x86 не нашлось места 16-разрядным регистрам. В x86 сишный тип short 16-разрядный, а в x86-64 — 32-разрядный. Это плохо для 16-битных данных.

Откуда такая информация?
Вот такой код:
short s;

int main()
{
s += sizeof(s);
}
Компилируется[https://godbolt.org/z/7r8abT] MSVC 64-bit в:
movsx   rax, WORD PTR short s
add rax, 2
mov WORD PTR short s, ax
А GCC 10.2 x86-64 в[https://godbolt.org/z/8as6KK]:
movzx   eax, WORD PTR s[rip]
add eax, 2
mov WORD PTR s[rip], ax
В обоих случаях sizeof(short) = 2.
И в сгенерированном коде есть обращение к «ax» (а это 16-разрядный регистр).

     2021/01/21 15:07, Автор сайта          # 

Наверное, неверно понятая невозможность запуска 16-разрядного кода на 64-разрядной Windows привела к такой мысли. Надо бы наконец подробнее изучить x86-64.

     2021/01/22 11:27, kt          # 

В 64-разрядном режиме 16-разрядные регистры не только сохранились, но и удвоились:
ax, bx, cx, dx, bp, sp, si, di добавлены r8w,r9w,r10w,r11w,r12w, r13w r14w, r15w
при этом стройная система названий одинаковой длины поломалась.

     2021/01/22 11:46, Автор сайта          # 

Регистры x86-64
Регистры x86-64

     2021/01/22 12:59, kt          # 

Оно самое. Но программа была неполной — не хватало марафонского бега ))
Не нарисованы используемые в программах регистры отладки DR0-DR7, к которым добавились DR8-DR15 и при этом в запоминаемом контексте исключения Windows 10 я никаких DR8-DR15 не наблюдаю...

     2021/03/27 02:02, Виталий Монастырский          # 

Считаю подход изначально недодуманным.
Во-первых, если текст программы будет написан не где-либо, кроме как в родном IDE, а тем более, если предполагается многокомпиляторная поддержка, то нам в любом случае придется сталкиваться с огромным количеством кодировок.
Во-вторых, мы никаким образом не избежим проблем с составными символами, поэтому в моем ЯП, я ввел такие типы данных, как рунический и глифовый тип данных.
Но на самом деле данная проблема решается очень простым методом — путем создания словаря автозамены. Точка.
Это тоже самое, что происходит при сжатии любого текста. Такой словарь всегда будет во много крат меньше, чем любой массив символов и для большинства программ, особенно написанных на латинице, вполне хватит и 8 бит, не то, что 16. Даже заморачиваться не стоит.
Берем исходный текст, транскрибируем его, составляем по нему словарь, и перекодируем его в соответствии с этим словарем.
Что же касается служебных слов и операторов языка, то здесь вообще нужно изначально использовать другой подход. Нужно сразу создавать словари служебных слов для разных языков и подключать их через служебные теги в самой программе. Например у меня это реализовано так: в начале файла идет служебная инструкция — на каком языке IDE должна показывать программу, дальше подключается соответствующий файл. Например, в русском файле условные операторы это "если/иначе", а английской версии "if/else". Берем нужное слово и подставляем вместо нужного оператора. А вот в самом исходном коде условие указывается в виде знака вопроса — ?. Вот и все.
Выходит, что чистый код — международный, там только знаки и символы. Все переменные и прочие элементы имеют номерные обозначения. Но в начале исходного кода размещается полная библиотека всех терминов на том языке, который использует программист, написавший программу. Соответственно, для того, чтобы увидеть программу на своем родном языке, нужно лишь сменить тег языка. При этом IDE подставит вместо знаков операторы Вашего родного языка, а вместо цифровых обозначений элементов языка — автоматический перевод этих слов с языка программиста, писавшего код, на Ваш родной. Естественно, что при этом должны использоваться полные слова родного языка в наименованиях. Но эта проблема так же решается. Для перевода может использоваться тот же автоматический переводчик гугла. Он не нужен постоянно онлайн, достаточно только один раз осуществить перевод словаря и все. Всех делов.

Добавить свой отзыв

Написать автору можно на электронную почту mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Содержание

Каким должен быть язык программирования?

Анализ и критика

Описание языка

Компилятор

●  Надо ли использовать YACC, LEX и подобные инструменты

●  Выбор кодировки для компилятора

●  Раскрутка компилятора

●  Лексический анализатор

●●  Разбор цепочек знаков операций

●●  Как отличить унарный минус от бинарного

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Новости и прочее




Последние отзывы

2021/04/22 18:55 ••• Неслучайный читатель
Некошерный «goto»

2021/04/19 17:01 ••• Клихальт
О наименовании проекта и языка программирования

2021/04/17 18:29 ••• Comdiv
Не поминайте всуе PL/1

2021/04/07 17:35 ••• :NONAME
Почему обречён язык Форт

2021/04/04 18:29 ••• kt
Переключатель

2021/04/04 18:04 ••• Александр Коновалов aka Маздайщик
Каким должен быть язык программирования?

2021/04/02 19:32 ••• Александр Коновалов aka Маздайщик
Циклы

2021/04/01 14:07 ••• Александр Коновалов aka Маздайщик
Условные операторы

2021/04/01 13:26 ••• Александр Коновалов aka Маздайщик
Комментарии автоматической генерации документации

2021/04/01 13:13 ••• Александр Коновалов aka Маздайщик
Длинные комментарии

2021/04/01 13:03 ••• Александр Коновалов aka Маздайщик
Некоторые «вкусности» Алгол-68

2021/04/01 12:15 ••• Александр Коновалов aka Маздайщик
Устарел ли текст как форма представления программы

2021/03/28 22:31 ••• Виталий Монастырский
Так ли нужны операции «&&», «||» и «^^»?

2021/03/28 13:39 ••• Виталий Монастырский
Бесплатный софт в мышеловке

2021/03/27 23:56 ••• Виталий Монастырский
Операции присвоения и проверки на равенство. Возможно ли одинаковое обозначение?