【问题标题】:reading lisp streams - unexpected infinite loop阅读 lisp 流 - 意外的无限循环
【发布时间】:2021-07-11 20:32:37
【问题描述】:

我正在尝试从流中读取“单词”。一个词只是一个数字“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))
           )
         ))))

【问题讨论】:

    标签: lisp common-lisp infinite-loop word


    【解决方案1】:

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

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

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

    【讨论】:

    • (get-output-stream-string out) 用于刷新当前内容。未使用它是因为检测到错误输入(即“test123”)。这就是为什么我用这条线丢弃这个输入。 whitespace-or-nil 上的无限循环肯定是一个问题。感谢您的调试代码,我希望我能用它解决所有其他问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-25
    • 1970-01-01
    • 2021-09-12
    • 2019-06-16
    • 1970-01-01
    • 2012-03-12
    • 1970-01-01
    相关资源
    最近更新 更多