Непрочитанное поведение, отклоняющееся от спецификации?

На странице 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,

Такое поведение является хорошей новостью для многосимвольного просмотра, если оно согласованно, но почему не выдается ошибка?

1 ответ

В ответ на комментарий пользователя 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]

Это говорит о том, что предположение Йкийски верно. Исходное поведение также наблюдалось, когда ввод читался из текстового файла, например так:

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

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

Я полагаю, что при работе с локальным файловым вводом-выводом реализация считывает большие куски файла в память, а затем read-char читает из буфера. Если это правильно, это также поддерживает предположение, что ошибка, описанная в спецификации, не генерируется типичными реализациями при чтении из потока, содержимое которого находится в памяти.

Другие вопросы по тегам