[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

An interesting puzzle, or bug in CLISP?

Here is an interesting puzzle involving the CLISP compiler,
PRINT-OBJECT, and reader-macros:

Assume from now on that the current package is COMMON-LISP-USER.

The following code does nothing useful, but demonstrates the problem.
We simply reprogram the Lisp reader so that a form such as !(a b c)
will be read in as a structure containing the list (a b c).  In
addition, we add a PRINT-OBJECT method to ensure that a foo-structure
is printed so that it can be read back in.

-----BEGIN FILE  good.lsp   --------------------------
(defclass foo ()
  ((char :initarg :char :reader foo-char)
   (form :initarg :form :reader foo-form)))

(set-macro-character #\!
  #'(lambda (s c) (make-instance 'foo :char c :form (read s T NIL T))))

(defmethod print-object ((x foo) stream)
  (format stream "~C~A" (foo-char x) (foo-form x)))

(setq test '!(a b c))
-----END FILE  good.lsp   --------------------------

This code works fine, as you can see ...

-----BEGIN good CLISP session -------
> (load "good.lsp")
;; Loading file good.lsp ...
The generic function #<GENERIC-FUNCTION PRINT-OBJECT> is being modified, but has already been called.
;; Loading of file good.lsp is finished.
> test
!(A B C)
> (type-of test)
-----END good CLISP session -------

If good.lsp is loaded then compiled, the resulting `good.fas' also
works fine.

Now suppose I modify the PRINT-OBJECT method very slightly, replacing
the format directive `~C' by the format directive `~A'. The result is
a file I'll call `bad.lsp'.

The punch line is that the _interpreted_ versions `good.lsp' and
`bad.lsp' both work, but the _compiled_ version `bad.fas' is
incorrect.  Apparently the Lisp constant object denoted by !(a b c) in
the source code `bad.lsp' is stored incorrectly in `bad.fas',
resulting in the following misbehavior:

-----BEGIN bad CLISP session -------
> (load "bad.fas")
;; Loading file bad.fas ...
The generic function #<GENERIC-FUNCTION PRINT-OBJECT> is being modified, but has already been called.
;; Loading of file bad.fas is finished.
> test

*** - EVAL: variable TEST has no value
1. Break> 
-----END bad CLISP session -------

The form  (setq test '!(a b c)) is never evaluated, presumably because
the reader expands this form into erroneous byte-code.

Here's a clue to the puzzle (edited):

-----BEGIN clue -------
diff bad.fas good.fas
<             #17Y(   ... byte codes omitted .....)
>             #20Y(   ... byte codes omitted .....)
< #Y(#:TOP-LEVEL-FORM-4 #11Y( ... byte codes ...) #\!(A B C) TEST)
> #Y(#:TOP-LEVEL-FORM-4 #11Y( ... byte codes ...) !(A B C) TEST)
-----END clue ---------

You can see how the source constant denoted by !(a b c) is stored
differently in bad.fas and good.fas.  But since, for example, the
format directives `~A' and `~C' behave the same way when applied to
characters, I expected the choice of format directive to make no
difference, in the intrepreted _or_ compiled versions.

BTW, `bad.fas' behaves properly if I change  #\!(A B C) to !(A B C)
(I know it's not a good idea to edit *.fas).

What is going on here?  Thanks for your insights!

Mark A. Thomas