Непрочитанное поведение, отклоняющееся от спецификации?
На странице Common Lisp HyperSpec для unread-char
- смотрите здесь - это говорит о двух следующих вещах:
"unread-char предназначен для того, чтобы быть эффективным механизмом, позволяющим считывателю Lisp и другим анализаторам выполнять односимвольный просмотр во входном потоке".
"Ошибка вызывать 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
читает из буфера. Если это правильно, это также поддерживает предположение, что ошибка, описанная в спецификации, не генерируется типичными реализациями при чтении из потока, содержимое которого находится в памяти.