openlang

Где остаётся курсор после неполной записи?

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

Предлагаю такую схему. Байтовый вектор принадлежит вызывающему коду, парсеру он одалживает разделяемый срез. Курсор и временно декодированные поля принадлежат парсеру. При успехе курсор переходит за одну полную запись. При неполном входе он остаётся в начале этой записи, а предварительные поля отбрасываются. При некорректном входе начало тоже сохраняется, а возвращается смещение ошибки. Диагностика неполного входа содержит начало записи и позицию, до которой дошло сканирование. Эта позиция говорит о том, что просмотрено, и ничего не потребляет.

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

Если забрать вектор во владение, появится свой вопрос, возвращать ли его при сбое. Заимствование этого вопроса не создаёт, но позицию сбоя и допустимые следующие вызовы всё равно стоит описать в документации.

Разговор продолжается 7

Следуя доводам
  1. Ссылка на ответ

    В OCaml такая диагностика выглядит как Incomplete (start, scanned). Образец выбирает конструктор и связывает обе позиции, а условие вроде when start = scanned отличает скан, который не сдвинулся, от сдвинувшегося. Написать Incomplete (p, p) в смысле равных позиций нельзя. Компилятор отвергает переменную, связанную дважды в одном образце.

    К потреблению всё это отношения не имеет. Форма лишь делает позиции удобными для просмотра. Сдвинулся ли курсор, по-прежнему обещает только задокументированное правило курсора.

  2. Ссылка на ответ

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

  3. Ссылка на ответ

    try всего лишь передаёт ошибку неполного входа вызывающему коду. Курсор он не восстанавливает и предварительные поля не отбрасывает, так что на Zig эту работу всё равно выполняет описанная вами реализация. С catch unreachable ещё хуже. Это утверждение, что неполного входа не бывает, и один сетевой фрагмент его опровергает. В сборке с проверками безопасности ложное утверждение приводит к аварийному завершению. Какой инвариант исключает эту ошибку? Предварительно проверенный кадр мог бы, но только если проверка и все последующие операции исключают каждую ошибку, которую покрывает утверждение.

  4. Ссылка на ответ

    То же самое, если парсер работает отдельным процессом BEAM и ему позволено падать на неполном входе. Курсор и предварительные поля пропадают вместе с ним. Процесс запущен заново. Что он может восстановить? Только то, что кто-то сохранил. Вызывающий код, у которого есть весь непотреблённый остаток, может отправить его новому процессу и разобрать заново. Если остаток никто не хранит, перезапуск недостающих байтов не вернёт. К тому же связи определяют, какие ещё процессы получат сигнал о сбое. Для обычной неполной записи лучше вернуть результат и ждать входных данных. Так за восстановлением гораздо проще уследить.

  5. Ссылка на ответ

    В типе-сумме start и scanned читаются однозначно. Вызывающий код на C вместо этого получает выходную структуру, и у неё должны быть собственные правила.

    Какие поля можно читать после неполного возврата? Обе позиции или только статус? Временные декодированные поля обнулены, остались от прошлого вызова или вообще недоступны? Если в заголовке об этом ничего нет, каждый вызывающий будет гадать, какие присваивания парсер успел сделать.

    Дальше указатели. Если диагностика указывает внутрь входных данных, документация должна это оговорить и указать, сколько времени эти данные обязаны оставаться действительными. В схеме заимствования из поста alden буфером владеет вызывающий, поэтому парсер его не освобождает. Правило, по которому free(NULL) ничего не делает, относится только к нулевому указателю. Права освобождать заимствованный вход оно парсеру не даёт.

  6. Ссылка на ответ

    Та же проблема уровнем выше. Если PHP-обёртка бросает исключение на неполном входе, блок catch узнаёт, что что-то сломалось, но не узнаёт, какие поля результата ещё можно читать. В итоге каждый вызывающий пишет свою проверку временных полей, и эти проверки быстро расходятся. Одна задокументированная форма ошибки в обёртке требует чуть больше кода сразу и гораздо меньше правок потом.

  7. Ссылка на ответ

    Каждый обычный возврат должен отдавать размеченный результат, где инициализированы все поля. Тогда стороне C гадать не о чем.

    Неполный результат сам хранит start и scanned как обычные числа и никакой временной записи не показывает. Результат с ошибкой формата хранит смещение, где возникла ошибка. Запись показывает только успех. Если эта запись заимствует вход, вызывающий дочитывает её до изменения буфера. Если запись владеющая, она полностью инициализирована и живёт независимо от буфера, ценой копирования. Для одной записи такая цена выглядит разумной.

    Тогда обёртка brandt_p может собрать своё PHP-значение ошибки из владеющей диагностики и никогда не смотреть на недописанные поля. С обёрткой, которая ловит исключение и преобразует его, всё иначе. Кому-то придётся записать, какие поля диагностики переживают исключение.