Lisp流读取解析单词时出现意外无限循环问题求助
问题详情
我正在尝试从stream中读取“单词”,单词分为三类:纯数字如“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)) ) ))))
问题原因与修复方案
核心问题
无限循环的触发点在read-initial状态的EOF处理逻辑:当流读取到末尾时,read-char返回nil,会命中第一个cond分支,执行peek-char t stream nil后再次跳回read-initial,反复读取EOF永远不会退出,形成死循环。
其他潜在问题
read-initial状态中的兜底返回分支永远无法命中,属于无效死代码,因为前面的条件已经覆盖了所有可能的字符类型。skip-til-next状态中调用get-output-stream-string out会清空已经写入输出流的有效内容,属于多余操作。read-initial状态中额外调用peek-char t没有必要,反而可能引入不可预期的流指针偏移。
修复后代码
(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 ;; 读到EOF直接终止返回 ((null c) (return-from read-word nil)) ;; 跳过空白字符直接重读 ((whitespace-or-nil-p c) (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))) skip-til-next ;; 清空无效缓存内容 (get-output-stream-string out) (loop (let ((next-c (peek-char nil stream nil))) (when (whitespace-or-nil-p next-c) (return)) (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)))))))
修复说明
- 新增EOF优先判断逻辑,读到流末尾直接返回
nil终止函数,彻底解决无限循环问题。 - 移除
read-initial状态中多余的peek-char t调用,避免不必要的流操作。 - 优化
skip-til-next的循环逻辑,显式判断peek结果,降低隐式状态异常风险。 - 移除
read-initial状态中无效的兜底返回分支。
内容的提问来源于stack exchange,提问作者BallKa
相关产品推荐
相关产品推荐

