Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум «Всё о Паскале» _ Переносимый Ассемблер (Си) _ Потоковая конвертация между UTF-8 и UTF-32

Автор: OCTAGRAM 31.10.2019 10:17

GNAT -gnat95

Выбранная тактика поддержки Юникода такова, что с системными файловыми API мы не морочимся, а вот с содержимым заморочиться можно и нужно. Уж байтики прочитанные-то понять надо суметь.

Требуется реализовать пассивный интерфейс конвертации туда и обратно по типу iconv. iconv принимает на вход буфер, на выход буфер, и сообщает позицию, где остановились чтение и запись. Также возвращается причина остановки. Реальный сишный iconv, если смотреть его API, ещё принимает размеры входного и выходного буфера, в нашем случае нужно полагаться на возможности языка Ада передавать длины массива и, если, допустим, из буфера нужно читать не сначала или в буфер писать не с начала, нарезать нужный кусочек массива синтаксисом Массив (Индекс .. Индекс).

ВАЖНО: всевозможные функции, возвращающие массивы, как правило, делают их с начальным индексом 1, но полагаться на это запрещено. Начинать перебирать любой массив нужно с 'First, причём, с правильного 'First, соответствующего массиву, а то я как раз в библиотеке GNAT где-то напоролся в конвертации UTF, что там 'First не от того массива берётся.

Под пассивностью подразумевается, что функция конвертации не выделяет память, а только пишет, куда ей дали.

У конвертации есть некоторое состояние. Я думаю, будет достаточно объявить type Conversion_UTF_32_To_UTF_8 is private; и type Conversion_UTF_8_To_UTF_32 is private;

Внутри состояние конечного автомата и недособранный 32битный символ, предпочтительно, Interfaces.Unsigned_32.

От реализации я бы очень хотел реализации разгона, когда оба буфера позволяют. В общем случае работа функции конвертации может происходить так:


На старте побайтовая обработка нужна, если состояние, в котором вызвали конвертацию, было не основным (готовность прочитать новый символ). В конце побайтовая обработка нужна, если ещё есть, что конвертировать во входном буфере, но пространство для манёвра во входном и выходном буферах не позволяет продолжать разгон. Может быть, его достаточно, но мы не знаем. Например, на входе один байт и на выходе место для одного 32битного символа, и байт оказался ASCII. Всё хорошо, но для разгона резерв недостаточен.

Чтобы разогнаться, нужно быть в основном состоянии и всё время разгона пребывать в нём, и чтобы на входе и выходе было пространство для любых возможных ситуаций. Например, при чтении UTF-8, чтобы поддерживать чтение суррогатных пар, закодированных в UTF-8, нужен резерв 6 октетов во входном буфере. А при записи, так как сейчас нам, кажется, не нужно писать в таком ублюдочном формате, хватит и 4 октетов в выходном буфере. На 32битные символы ограничения 1 штука. Но, правда, если на выходе уже нет места даже для одного 32битного символа, а на входе ещё есть байты, кажется правильным побайтово прочитать все байты, кроме последнего, наработав состояние.

Требуется поддержать:
валидный UTF-8 и валидный UTF-32
ничего сверх 17 стандартных плоскостей Юникода валидным не является
и также ситуацию, когда в UTF-8 закодированы две суррогатные пары, но только в таком виде

То есть, одиночные суррогатные пары — это не валидно.

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

Тип данных 32битных строк — String_32, который переименованный Wide_Wide_String. Октеты в Stream_Element_Array ожидаются.

Нужны тесты, которые по-всякому ровно и неровно по границам пихают данные корректные и некорректные.

Автор: Sergey Dukov 31.10.2019 11:08

Если только UTF-8 <--> UTF-32, то посмотрите внимательно реализацию пакета Unbouded_Array. Может быть всё что нужно там уже есть. Все определения символов и строк специфицированы в файлах Ada_Magic_Forward.ads, Ada_Magic_Forward.Character_32s.ads и Ada_Magic_Forward.Strings.ads неким универсальным способом. Тип Character_32 я с самого начала определил как беззнаковый (мне для преобразования нужны были bitwize-операции). Все операции преобразования реализованы в пакете Unbounded_array. При преобразованиях определены способы реагирования на BOM. Если в UTF-8 строке недостаёт символов, генерируется исключения. Может просто дополнить пакет Unbouded_Array процедурами работы с бинарными потоками ЮНИКОДА? В проверку валидности включить проверку на присутствие символа в исключаемом диапазоне ЮНИКОДА?

Автор: OCTAGRAM 31.10.2019 11:48

Цитата(Sergey Dukov @ 31.10.2019 11:08) *
Тип Character_32 я с самого начала определил как беззнаковый (мне для преобразования нужны были bitwize-операции)


Нет, подход не верный. Нужно через 'Pos и 'Val приводить к Interfaces.Unsigned_32.

Стоит сделать уточнение, что в режиме Ada 2005+ тоже должно быть возможно собрать, подменив какие-то пакеты, чтоб не делали искусственные типы, а брали стандартные. В этом случае Wide_Wide_* в явном виде будет по всей программе использоваться, и слетит вся битовая арифметика.

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

То, что относится к потокам, в массивы класть не стоит. Скорее, конвертацию можно в одном пакете собрать и массивов и фрагментов потоков.


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

Автор: Sergey Dukov 31.10.2019 12:29

При конвертации строк холостой проход у меня делается всегда, чтобы определить можно ли использовать существующий буфер или разместить новый. При конвертации потоков можно бы было использовать циклический буфер, но это бы нарушило понятие стандартного потока. Может быть расширить понятие бинарного потока для циклических буферов? Алгоритмы циклических буферов широко используются в системном программировании и в цифровых фильтрах. Собственно преобразование потоков по сути и есть классический цифровой фильтр.

Автор: OCTAGRAM 31.10.2019 13:11

Требуемый програмный интерфейс достаточно универсален, чтоб можно было закодировать поверх него и циклический буфер тоже. Если последний раз остановились где-то посередине, передаём срез в конце массива в конвертор. Потом передаём срез в начале массива в конвертор.

Автор: Sergey Dukov 31.10.2019 19:13

А не подскажете, есть ли сейчас в стандартной библиотеке АДы конвейеры FIFO?

Автор: OCTAGRAM 1.11.2019 2:35

Очередь и куча там есть, но это для многопоточных программ.

http://www.ada-auth.org/standards/12rat/html/Rat12-8-6.html

Я так понимаю, Ada.Containers.Bounded_Synchronized_Queues будет именно циклической.