3

在 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"

它不会引发错误(在 SBCL 或 CCL 上,我尚未在其他实现上对其进行测试)但我看不出如何在流之间发生任何读取操作(隐式或显式)连续调用unread-char.

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

4

1 回答 1

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))

然后我在第二个 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)

第一个 REPL 中的以下内容:

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

(create-client 4000)

它确实引发了我预期的错误:

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

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

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

(with-open-file (stream "test.txt")
  (unread-char-test stream)))
==> "hello"

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

于 2017-07-29T16:00:47.130 回答