读取 lisp 流 - 意外的无限循环

reading lisp streams - unexpected infinite loop

我正在尝试从流中读取“单词”。一个词只是一个数字“1234”,一个通常的字母字符词“test”和带有撇号“you're”的常用词的缩写。

当然有数十亿种方法可以做到这一点。我正在尝试使用 tagbody 来实现类似状态机的功能来解析单词。如果给出了正确的输入,我的实现就可以工作,但对于不符合单词的输入则失败。我试图跳过输入,直到达到新的空白或 eof,但这给了我一个无限循环,我不知道为什么。

有人可以解释一下吗?

这是我的代码

(defun whitespace-or-nil-p (c)
  "Returns if a character is whitespace"
  (member c '(#\  #\Tab #\Return #\Newline nil)))

(defun read-word (stream)
  (let ((c nil))
    (with-output-to-string (out)
      (tagbody
       read-initial
         (setf c (read-char stream nil))
         (cond
           ((whitespace-or-nil-p c) (peek-char t stream nil) (go read-initial))
           ((not (alphanumericp c)) (go skip-til-next))
           ((digit-char-p c) (go read-number))
           ((alpha-char-p c) (go read-simple-or-contracted-word))
           (t (return-from read-word))
           )
       skip-til-next
         (get-output-stream-string out)
         (loop until (whitespace-or-nil-p (peek-char nil stream nil)) do (read-char stream nil))
         (go read-initial)
       read-number
         (write-char c out)
         (setf c (read-char stream nil))
         (cond
           ((whitespace-or-nil-p c) 
              (return-from read-word (get-output-stream-string out)))
           ((not (digit-char-p c)) (go skip-til-next))
           (t (go read-number))
           )
       read-simple-or-contracted-word
         (write-char c out)
         (setf c (read-char stream nil))
         (cond
           ((whitespace-or-nil-p c) 
              (return-from read-word (get-output-stream-string out)))
           ((and (char/= c #\') (not (alpha-char-p c))) (go skip-til-next))
           (t (go read-simple-or-contracted-word))
           )
         ))))

这是您的代码,已修改以防止无限循环,以便对其进行调试。 我在代码更改的地方添加了注释:

(defun read-word (stream)
  (let ((c nil) 
        ;; how many times we allow the code to enter dbg function
        (counter 10))
    (flet ((dbg (symbol &rest args)
             ;; each time it is called, we decrease counter, when it
             ;; reaches zero, we stop the state machine
             (print (list* symbol args))
             (when (<= (decf counter) 0)
               (return-from read-word :too-many-loops))))
      (with-output-to-string (out)
        (tagbody
         read-initial
           (setf c (read-char stream nil))
           (dbg 'read-initial c)
           (cond
             ((whitespace-or-nil-p c) (peek-char t stream nil) (go read-initial))
             ((not (alphanumericp c)) (go skip-til-next))
             ((digit-char-p c) (go read-number))
             ((alpha-char-p c) (go read-simple-or-contracted-word))
             (t (return-from read-word))
             )
         skip-til-next
           (dbg 'skip-til-next)
           (get-output-stream-string out)
           (loop until (whitespace-or-nil-p (peek-char nil stream nil)) do (read-char stream nil))
           (go read-initial)
         read-number
           (dbg 'read-number)
           (write-char c out)
           (setf c (read-char stream nil))
           (cond
             ((whitespace-or-nil-p c) 
              (return-from read-word (get-output-stream-string out)))
             ((not (digit-char-p c)) (go skip-til-next))
             (t (go read-number))
             )
         read-simple-or-contracted-word
           (dbg 'read-simple-or-contracted-word)
           (write-char c out)
           (setf c (read-char stream nil))
           (cond
             ((whitespace-or-nil-p c) 
              (return-from read-word (get-output-stream-string out)))
             ((and (char/= c #\') (not (alpha-char-p c))) (go skip-til-next))
             (t (go read-simple-or-contracted-word))
             )
           )))))

这是我能想到的最简单的测试用例:

* (with-input-from-string (in "") (read-word in))
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
(READ-INITIAL NIL) 
:TOO-MANY-LOOPS

所以你需要处理 read-char returns nil 的情况。目前,它被视为空格,并且在这种情况下发生的对 peek-char 的调用不会消耗底层流中的字符(它到达文件末尾);例如,您可以观察 peek-char 的 return 值,以避免无限地返回到 read-initial 标签。

我也怀疑 (get-output-stream-string out) 应该做什么,尤其是当你调用它而不使用它的 return 值时。例如,我会接受一个回调函数并在读取每个标记时调用它。