读取字节忽略 SBCL 灰色流中的 eof-error-p

read-byte ignores eof-error-p inside SBCL gray stream

所以,我专门研究 read-byte 在 SBCL 上使用灰色流的方法。我 运行 出现了一种奇怪的行为,其中 eof-error-p 参数似乎被忽略了。可能是我在我的代码中遗漏了一些微不足道的错误,但我已经看了十几遍了,就是没看到。

假设有一个文件test.txt只有一个字节,比方说

echo -n 7 > test.txt

我希望下面的代码能够 return :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 异常。这里发生了什么?

我可以重现这个例子,调试器显示(在 Slime 下):

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

通过将光标移动到第0帧(即read-byte)并按v,编辑器在[=14=中显示read-byte的定义](我从源代码构建它):

(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,也不好,因为 returned 值不是字节。但是如果你 return -1,测试通过(源流是无符号字节流,但是你的包装器可能 return -1 而没有错误)。

这是对 coredump 答案的补充。特别是我认为您的代码的行为是正确的:SBCL 在其灰色流的实现中做了正确的事情,它确实应该在这里发出异常信号。

在您的代码中,调用模式是:

  • read-byte(没有可选参数)在您的 binary-input-stream 上调用 stream-read-byte 同一流;
  • 你在 stream-read-byte 上的方法直接调用你包装的流上的 read-byte,要求没有错误和 :eof 的 EOF 上的 return,并且 returns 该调用的值,无需进一步检查;
  • 这大概会通过包装流的 stream-read-byte 方法,但我们不必担心。

那么,这应该怎么办?好吧,我不确定 Gray 流的最终文档的正确位置在哪里,但是 here is something which may be close to it.

在该文档中,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))

(我认为最后两个定义确实意味着 'the implementation needs to do something whose behaviour is equivalent to this',特别是在流是灰色流的情况下需要这样做。)

所以 stream-read-byte 上的任何方法都不能 return :eof 除非流位于文件末尾,特别是这样做将导致发出异常信号(或者将导致 report-eof 被调用,无论如何,它可能会或可能不会发出异常信号)。而:eof唯一的特殊值,stream-read-byte可以return。

好吧,你在 stream-read-byte 上的方法确实 return :eof 指示文件结束,小心地抑制了内部调用 yo read-byte 否则会出现的异常信号,所以这是一个很好的方法。

但是随后 outerread-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))))

对于单字节文件,这应该 return 文件中的字节和 :eof.