Выбранная тактика поддержки Юникода такова, что с системными файловыми 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 ожидаются.
Нужны тесты, которые по-всякому ровно и неровно по границам пихают данные корректные и некорректные.