I was trying to make a simple parser in elisp, and I encountered a problem where I defvar
a global, then later I setq
a new value into it. This works the first time. However, later setq
's fail every time.
The following code is a simplication of the problem:
(defvar buf '(BUF))
(defvar head nil)
(defun pparse (seq)
(defun status ()
(princ (format "Parse: %40s || %-20s\n"
(prin1-to-string seq) (prin1-to-string buf))))
(while seq
(status)
(setq head (car seq))
(setq seq (cdr seq))
(cond ((equal "x" head)
(nconc buf (list head)))
((equal "," head)
(setq buf '(BUF))
;;(setcdr buf nil) <- fixes it but doesn't answer my question
)))
(status))
(pparse '("x" "," "x" "," "x" "," "x"))
Which produces this output:
Parse: ("x" "," "x" "," "x" "," "x") || (BUF)
Parse: ("," "x" "," "x" "," "x") || (BUF "x")
Parse: ("x" "," "x" "," "x") || (BUF)
Parse: ("," "x" "," "x") || (BUF "x")
Parse: ("x" "," "x") || (BUF "x")
Parse: ("," "x") || (BUF "x" "x")
Parse: ("x") || (BUF "x" "x")
Parse: nil || (BUF "x" "x" "x")
As you can see, the second column was clipped once, but grew every subsequent time.
If you uncomment the setcdr
line, this works as expected (output is as follows). You can even dump the setq
. I understand why this fixes it, but not why the original error happens in the first place.
Parse: ("x" "," "x" "," "x" "," "x") || (BUF)
Parse: ("," "x" "," "x" "," "x") || (BUF "x")
Parse: ("x" "," "x" "," "x") || (BUF)
Parse: ("," "x" "," "x") || (BUF "x")
Parse: ("x" "," "x") || (BUF)
Parse: ("," "x") || (BUF "x")
Parse: ("x") || (BUF)
Parse: nil || (BUF "x")
BTW, the behavior is the same even if i turn off lexical scoping.