read-byte игнорирует eof-error-p внутри серого потока SBCL - PullRequest
2 голосов
/ 08 апреля 2020

Итак, я специализируюсь на read-byte методе с использованием серых потоков на SBCL. Я столкнулся со странным поведением, когда аргумент eof-error-p, кажется, игнорируется. Возможно, в моем коде пропущена какая-то тривиальная ошибка, но я уже просмотрел ее десятки раз и просто ее не вижу.

Предположим, есть файл test.txt с один байт, скажем

echo -n 7 > test.txt

Я ожидаю, что следующий код вернет :eof

(defclass binary-input-stream (fundamental-binary-input-stream)
  ((stream :initarg :stream :reader stream-of)))

(defmethod stream-read-byte ((stream binary-input-stream))
  (format t "~a~%" "It's me, all right")
  (read-byte (stream-of stream) nil :eof))

(defun make-binary-input-stream (stream)
  (make-instance 'binary-input-stream :stream stream))

(with-open-file (in "test.txt" :element-type '(unsigned-byte 8))
  (setq in (make-binary-input-stream in))
  (read-byte in)
  (read-byte in))

Однако SBCL выдает исключение END-OF-FILE. Что здесь происходит?

Ответы [ 2 ]

3 голосов
/ 09 апреля 2020

Это дополнение к ответу coredump. В частности, я думаю, что поведение вашего кода правильное: SBCL делает правильные вещи в реализации потоков Грея, и он действительно должен сигнализировать об исключении здесь.

В вашем коде шаблон вызовов :

  • read-byte (без дополнительных аргументов) для ваших binary-input-stream вызовов stream-read-byte в том же потоке;
  • ваш метод на stream-read-byte напрямую вызывает read-byte в потоке, который вы завершили, не запрашивая ошибок и возвращая EOF :eof, и возвращает значение этого вызова без дальнейшей проверки;
  • , который, в свою очередь, предположительно проходит через метод stream-read-byte завернутый поток, но нам не нужно об этом беспокоиться.

Итак, что это должно делать? Ну, я не уверен, где правильное место для окончательного документирования серых потоков, но здесь есть нечто, что может быть близко к нему .

В этом документе stream-read-byte определяется:

STREAM-READ-BYTE  stream            [Generic Function]

    Used by READ-BYTE; returns either an integer, or the symbol :EOF if the
    stream is at end-of-file.

read-byte определяется как:

(defun READ-BYTE (binary-input-stream &optional (eof-errorp t) eof-value)
  (check-for-eof (stream-read-byte binary-input-stream) 
                 binary-input-stream eof-errorp eof-value))

Наконец check-for-eof определяется как:

(defun check-for-eof (value stream eof-errorp eof-value)
  (if (eq value :eof)
      (report-eof stream eof-errorp eof-value)
    value))

(я думаю, что последний два определения действительно означают, что «реализация должна делать что-то, чье поведение эквивалентно этому», и, в частности, это нужно делать в случае, когда поток является потоком Грея.)

Так что любой метод на stream-read-byte не должен возвращать :eof, если поток не находится в конце файла, и, в частности, это приведет к сигналу исключения (или, в любом случае, к вызову report-eof, и это может или не может сигнализировать об исключении). И :eof - это специальное значение only , которое может возвращать stream-read-byte.

Что ж, ваш метод на stream-read-byte действительно возвращает :eof, чтобы указать конец файла, тщательно исключено исключение, что внутренний вызов yo read-byte мог бы сигнализировать иначе, так что это метод с хорошим поведением.

Но тогда external вызов read-byte, как определено выше, видит это значение EOF и покорно вызывает исключение для вас. На самом деле это то, что вы просили, потому что вы не просили подавить исключение в этих вызовах.

Если вы не хотите исключения, вам нужно убедиться, что внешние вызовы read-byte попросите, чтобы одно не произошло, например:

(with-open-file (in "test.txt" :element-type '(unsigned-byte 8))
  (with-open-stream (bin (make-binary-input-stream in))
    (values (read-byte bin nil ':eof)
            (read-byte bin nil ':eof))))

Для однобайтового файла это должно вернуть байт в файле и :eof.

3 голосов
/ 09 апреля 2020

Я могу воспроизвести пример, и отладчик показывает (под Slime):

Backtrace:
  0: (READ-BYTE #<BINARY-INPUT-STREAM {1039A8D933}> T NIL)
  1: ((LAMBDA ()))
  ...

Переместив курсор на кадр 0 (т.е. read-byte) и нажав v , редактор показывает определение read-byte в sbcl/src/code/stream.lisp (я строю его из исходного кода):

(defun read-byte (stream &optional (eof-error-p t) eof-value)
  (declare (explicit-check))
  (if (ansi-stream-p stream)
      (ansi-stream-read-byte stream eof-error-p eof-value nil)
      ;; must be Gray streams FUNDAMENTAL-STREAM
      (let ((byte (stream-read-byte stream)))
        (if (eq byte :eof)
            (eof-or-lose stream eof-error-p eof-value) ;; <<<< CURSOR HERE
            (the integer byte)))))

Оказывается, :eof уже используется SBCL для указания enf строки, и так как вызов верхнего уровня read-byte не игнорирует ошибки, это приводит к тому, что условие сигнализируется.

Замена ключевого слова :eof другим, скажем, :my-eof, также не годится, так как возвращаемое значение значение не является байтом. Но если вы вернете -1, тест пройден (исходный поток является потоком байтов без знака, но ваша оболочка может вернуть -1 без ошибок).

...