unread-char行为偏离规范?

And*_*age 3 io sbcl common-lisp stream ccl

在Common Lisp HyperSpec页面上unread-char- 请参阅 此处 - 它说明了以下两点:

  1. "unread-char旨在成为允许Lisp读者和其他解析器在输入流中执行单字符预测的有效机制."

  2. "在同一个流上连续两次调用unread-char是错误的,而没有在该流上调用read-char(或其他隐式读取字符的输入操作)."

我正在研究如何为我正计划编写的解析器添加对CL流的多字符前瞻支持,并且为了确认上述内容,我运行了以下代码:

(defun unread-char-test (data)
  (with-input-from-string (stream data)
    (let ((stack nil))
      (loop
         for c = (read-char stream nil)
         while c
         do (push c stack))
      (loop
         for c = (pop stack)
         while c
         do (unread-char c stream)))
    (coerce
     (loop
        for c = (read-char stream nil)
        while c
        collect c)
     'string)))

(unread-char-test "hello")
==> "hello"
Run Code Online (Sandbox Code Playgroud)

它没有抛出错误(在SBCL或CCL上,我还没有在其他实现上测试它)但我没有看到可能有任何读取操作(隐式或显式)发生在流之间连续打电话给unread-char.

这种行为对于多字符前瞻来说是好消息,只要它是一致的,但为什么不抛出错误?

And*_*age 5

为了回应用户jkiiski的评论,我做了一些挖掘.我定义了一个类似于上面的函数,但它将流作为参数(为了更容易重用):

(defun unread-char-test (stream)
  (let ((stack nil))
    (loop
       for c = (read-char stream nil)
       while c
       do (push c stack))
    (loop
       for c = (pop stack)
       while c
       do (unread-char c stream)))
  (coerce
   (loop
      for c = (read-char stream nil)
      while c
      collect c)
   'string))
Run Code Online (Sandbox Code Playgroud)

然后我在第二个REPL中运行以下内容:

(defun create-server (port)
  (usocket:with-socket-listener (listener "127.0.0.1" port)
    (usocket:with-server-socket (connection (usocket:socket-accept listener))
      (let ((stream (usocket:socket-stream connection)))
        (print "hello" stream)))))

(create-server 4000)
Run Code Online (Sandbox Code Playgroud)

以及第一个REPL中的以下内容:

(defun create-client (port)
  (usocket:with-client-socket (connection stream "127.0.0.1" port)
    (unread-char-test stream)))

(create-client 4000)
Run Code Online (Sandbox Code Playgroud)

它确实抛出了我预期的错误:

Two UNREAD-CHARs without intervening READ-CHAR on #<BASIC-TCP-STREAM ISO-8859-1 (SOCKET/4) #x302001813E2D>
   [Condition of type SIMPLE-ERROR]
Run Code Online (Sandbox Code Playgroud)

这表明jkiiski的假设是正确的.从文本文件中读取输入时也会观察到原始行为,如下所示:

(with-open-file (stream "test.txt" :direction :output)
  (princ "hello" stream))

(with-open-file (stream "test.txt")
  (unread-char-test stream)))
==> "hello"
Run Code Online (Sandbox Code Playgroud)

我想,在处理本地文件I/O时,实现会将大块文件read-char读入内存,然后从缓冲区中读取.如果正确,这也支持这样的假设:当从内容在内存中的流中取消读取时,典型实现不会抛出规范中描述的错误.