0.8.11.19:
[sbcl.git] / src / code / describe.lisp
index cf4e87f..ca2ab73 100644 (file)
@@ -1,4 +1,4 @@
-;;;; most of the DESCRIBE mechanism -- that part which isn't derived
+;;;; most of the DESCRIBE system -- that part which isn't derived
 ;;;; from PCL code
 
 ;;;; This software is part of the SBCL system. See the README file for
 ;;;; from PCL code
 
 ;;;; This software is part of the SBCL system. See the README file for
 
 (in-package "SB-IMPL") ;(SB-IMPL, not SB!IMPL, since we're built in warm load.)
 \f
 
 (in-package "SB-IMPL") ;(SB-IMPL, not SB!IMPL, since we're built in warm load.)
 \f
-(defvar *describe-indentation-step* 3
-  #+sb-doc
-  "the number of spaces that sets off each line of a recursive description")
-
 (declaim (ftype (function (t stream)) describe-object))
 (declaim (ftype (function (t stream)) describe-object))
-(defgeneric describe-object ((x t) stream))
+(defgeneric describe-object (x stream))
 
 (defun describe (x &optional (stream-designator *standard-output*))
   #+sb-doc
   "Print a description of the object X."
   (let ((stream (out-synonym-of stream-designator)))
 
 (defun describe (x &optional (stream-designator *standard-output*))
   #+sb-doc
   "Print a description of the object X."
   (let ((stream (out-synonym-of stream-designator)))
-    (pprint-logical-block (stream nil)
-      (fresh-line stream)
-      (describe-object x stream)
-      (fresh-line stream)))
+    ;; Until sbcl-0.8.0.x, we did
+    ;;   (FRESH-LINE STREAM)
+    ;;   (PPRINT-LOGICAL-BLOCK (STREAM NIL)
+    ;;     ...
+    ;; here. However, ANSI's specification of DEFUN DESCRIBE,
+    ;;   DESCRIBE exists as an interface primarily to manage argument
+    ;;   defaulting (including conversion of arguments T and NIL into
+    ;;   stream objects) and to inhibit any return values from
+    ;;   DESCRIBE-OBJECT. 
+    ;; doesn't mention either FRESH-LINEing or PPRINT-LOGICAL-BLOCKing,
+    ;; and the example of typical DESCRIBE-OBJECT behavior in ANSI's
+    ;; specification of DESCRIBE-OBJECT will work poorly if we do them
+    ;; here. (The example method for DESCRIBE-OBJECT does its own
+    ;; FRESH-LINEing, which is a physical directive which works poorly
+    ;; inside a pretty-printer logical block.)
+    (describe-object x stream)
+    ;; We don't TERPRI here either (any more since sbcl-0.8.0.x), because
+    ;; again ANSI's specification of DESCRIBE doesn't mention it and
+    ;; ANSI's example of DESCRIBE-OBJECT does its own final TERPRI.
+    )
   (values))
 \f
 ;;;; miscellaneous DESCRIBE-OBJECT methods
 
 (defmethod describe-object ((x t) s)
   (values))
 \f
 ;;;; miscellaneous DESCRIBE-OBJECT methods
 
 (defmethod describe-object ((x t) s)
-  (format s "~@<~S ~_is a ~S.~:>" x (type-of x)))
+  (format s "~&~@<~S ~_is a ~S.~:>~%" x (type-of x)))
 
 (defmethod describe-object ((x cons) s)
   (call-next-method)
 
 (defmethod describe-object ((x cons) s)
   (call-next-method)
-  (when (and (legal-function-name-p x)
+  (when (and (legal-fun-name-p x)
             (fboundp x))
             (fboundp x))
-    (%describe-function (fdefinition x) s :function x)
+    (%describe-fun (fdefinition x) s :function x)
     ;;was: (format s "~@:_Its FDEFINITION is ~S.~@:_" (fdefinition x))
     ;; TO DO: should check for SETF documentation.
     ;; TO DO: should make it clear whether the definition is a
     ;;was: (format s "~@:_Its FDEFINITION is ~S.~@:_" (fdefinition x))
     ;; TO DO: should check for SETF documentation.
     ;; TO DO: should make it clear whether the definition is a
     ))
 
 (defmethod describe-object ((x array) s)
     ))
 
 (defmethod describe-object ((x array) s)
-  (let ((rank (array-rank x)))
-    (cond ((> rank 1)
-          (format s "~S ~_is " x)
-          (write-string (if (%array-displaced-p x) "a displaced" "an") s)
-          (format s " array of rank ~S." rank)
-          (format s "~@:_Its dimensions are ~S." (array-dimensions x)))
-         (t
-          (format s
-                  "~@:_~S is a ~:[~;displaced ~]vector of length ~S." x
-                  (and (array-header-p x) (%array-displaced-p x)) (length x))
-          (when (array-has-fill-pointer-p x)
-            (format s "~@:_It has a fill pointer, currently ~S."
-                    (fill-pointer x))))))
-  (let ((array-element-type (array-element-type x)))
-    (unless (eq array-element-type t)
-      (format s
-             "~@:_Its element type is specialized to ~S."
-             array-element-type))))
+  (fresh-line s)
+  (pprint-logical-block (s nil)
+    (cond
+     ((= 1 (array-rank x))
+      (format s "~S is a vector with ~D elements."
+             x (car (array-dimensions x)))
+      (when (array-has-fill-pointer-p x)
+       (format s "~@:_It has a fill pointer value of ~S."
+               (fill-pointer x))))
+     (t
+      (format s "~S is an array of dimension ~:S."
+             x (array-dimensions x))))
+    (let ((array-element-type (array-element-type x)))
+      (unless (eq array-element-type t)
+       (format s
+               "~@:_Its element type is specialized to ~S."
+               array-element-type)))
+    (if (and (array-header-p x) (%array-displaced-p x))
+       (format s "~@:_The array is displaced with offset ~S."
+               (%array-displacement x))))
+  (terpri s))
 
 (defmethod describe-object ((x hash-table) s)
   (declare (type stream s))
 
 (defmethod describe-object ((x hash-table) s)
   (declare (type stream s))
-  (format s "~@<~S ~_is an ~S hash table.~:>" x (hash-table-test x))
-  (format s "~_Its SIZE is ~S." (hash-table-size x))
+  (format s "~&~@<~S ~_is an ~S hash table.~:>" x (hash-table-test x))
+  (format s "~&Its SIZE is ~S." (hash-table-size x))
   (format s
   (format s
-         "~@:_~@<Its REHASH-SIZE is ~S. ~_Its REHASH-THRESHOLD is ~S.~:>"
+         "~&~@<Its REHASH-SIZE is ~S. ~_Its REHASH-THRESHOLD is ~S.~:>"
          (hash-table-rehash-size x)
          (hash-table-rehash-threshold x))
          (hash-table-rehash-size x)
          (hash-table-rehash-threshold x))
-  (let ((count (hash-table-count x)))
-    (format s "~@:_It holds ~S key/value pair~:P~:[: ~2I~_~;.~]"
-           count (zerop count))
-    (let ((n 0))
-      (declare (type index n))
-      (dohash (k v x)
-       (unless (zerop n)
-         (write-char #\space s))
-       (incf n)
-       (when (and *print-length* (> n *print-length*))
-         (format s "~:_...")
-         (return))
-       (format s "~:_(~@<~S ~:_~S~:>)" k v)))))
+  (fresh-line s)
+  (pprint-logical-block (s nil)
+    (let ((count (hash-table-count x)))
+      (format s "It holds ~S key/value pair~:P~:[: ~2I~_~;.~]"
+             count (zerop count))
+      (let ((n 0))
+       (declare (type index n))
+       (dohash (k v x)
+         (unless (zerop n)
+           (write-char #\space s))
+         (incf n)
+         (when (and *print-length* (> n *print-length*))
+           (format s "~:_...")
+           (return))
+         (format s "~:_(~@<~S ~:_~S~:>)" k v)))))
+  (terpri s))
 
 (defmethod describe-object ((condition condition) s)
   (sb-kernel:describe-condition condition s))
 \f
 ;;;; DESCRIBE-OBJECT methods for symbols and functions, including all
 ;;;; sorts of messy stuff about documentation, type information,
 
 (defmethod describe-object ((condition condition) s)
   (sb-kernel:describe-condition condition s))
 \f
 ;;;; DESCRIBE-OBJECT methods for symbols and functions, including all
 ;;;; sorts of messy stuff about documentation, type information,
-;;;; packaging, function implementation, etc..
+;;;; packaging, function implementation, etc...
 
 ;;; Print the specified kind of documentation about the given NAME. If
 ;;; NAME is null, or not a valid name, then don't print anything.
 
 ;;; Print the specified kind of documentation about the given NAME. If
 ;;; NAME is null, or not a valid name, then don't print anything.
   (when (and name (typep name '(or symbol cons)))
     (let ((doc (fdocumentation name kind)))
       (when doc
   (when (and name (typep name '(or symbol cons)))
     (let ((doc (fdocumentation name kind)))
       (when doc
-       (format s "~_~@(~A documentation:~)~@:_  ~A"
+       (format s "~&~@(~A documentation:~)~%  ~A"
                (or kind-doc kind) doc))))
   (values))
 
                (or kind-doc kind) doc))))
   (values))
 
 ;;; up as a name. (In the case of anonymous closures and other
 ;;; things, it might not be.) TYPE-SPEC is the function type specifier
 ;;; extracted from the definition, or NIL if none.
 ;;; up as a name. (In the case of anonymous closures and other
 ;;; things, it might not be.) TYPE-SPEC is the function type specifier
 ;;; extracted from the definition, or NIL if none.
-(declaim (ftype (function (t stream t)) %describe-function-name))
-(defun %describe-function-name (name s type-spec) 
+(declaim (ftype (function (t stream t)) %describe-fun-name))
+(defun %describe-fun-name (name s type-spec) 
   (when (and name (typep name '(or symbol cons)))
     (multiple-value-bind (type where)
   (when (and name (typep name '(or symbol cons)))
     (multiple-value-bind (type where)
-       (if (or (symbolp name) (and (listp name) (eq (car name) 'setf)))
+       (if (legal-fun-name-p name)
            (values (type-specifier (info :function :type name))
                    (info :function :where-from name))
            (values type-spec :defined))
       (when (consp type)
            (values (type-specifier (info :function :type name))
                    (info :function :where-from name))
            (values type-spec :defined))
       (when (consp type)
-       (format s "~@:_Its ~(~A~) argument types are:~@:_  ~S"
+       (format s "~&Its ~(~A~) argument types are:~%  ~S"
                where (second type))
                where (second type))
-       (format s "~@:_Its result type is:~@:_  ~S" (third type))))
+       (format s "~&Its result type is:~%  ~S" (third type))))
     (let ((inlinep (info :function :inlinep name)))
       (when inlinep
        (format s
     (let ((inlinep (info :function :inlinep name)))
       (when inlinep
        (format s
-               "~@:_It is currently declared ~(~A~);~
+               "~&It is currently declared ~(~A~);~
                 ~:[no~;~] expansion is available."
                 ~:[no~;~] expansion is available."
-               inlinep (info :function :inline-expansion name))))))
+               inlinep (info :function :inline-expansion-designator name))))))
 
 ;;; Print information from the debug-info about where CODE-OBJ was
 ;;; compiled from.
 
 ;;; Print information from the debug-info about where CODE-OBJ was
 ;;; compiled from.
     (when info
       (let ((sources (sb-c::debug-info-source info)))
        (when sources
     (when info
       (let ((sources (sb-c::debug-info-source info)))
        (when sources
-         (format s "~@:_On ~A it was compiled from:"
+         (format s "~&On ~A it was compiled from:"
                  ;; FIXME: The FORMAT-UNIVERSAL-TIME calls in the system
                  ;; should become more consistent, probably not using
                  ;; any nondefault options.
                  ;; FIXME: The FORMAT-UNIVERSAL-TIME calls in the system
                  ;; should become more consistent, probably not using
                  ;; any nondefault options.
            (let ((name (sb-c::debug-source-name source)))
              (ecase (sb-c::debug-source-from source)
                (:file
            (let ((name (sb-c::debug-source-name source)))
              (ecase (sb-c::debug-source-from source)
                (:file
-                (format s "~@:_~A~@:_  Created: " (namestring name))
+                (format s "~&~A~@:_  Created: " (namestring name))
                 (format-universal-time s (sb-c::debug-source-created
                                           source)))
                 (format-universal-time s (sb-c::debug-source-created
                                           source)))
-               (:lisp (format s "~@:_~S" name))))))))))
+               (:lisp (format s "~&~S" name))))))))))
 
 ;;; Describe a compiled function. The closure case calls us to print
 ;;; the guts.
 
 ;;; Describe a compiled function. The closure case calls us to print
 ;;; the guts.
-(defun %describe-function-compiled (x s kind name)
+(defun %describe-fun-compiled (x s kind name)
   (declare (type stream s))
   (declare (type stream s))
-  ;; FIXME: The lowercaseness of %SIMPLE-FUN-ARGLIST results, and the
-  ;; non-sentenceness of the "Arguments" label, makes awkward output.
-  ;; Better would be "Its arguments are: ~S" (with uppercase argument
-  ;; names) when arguments are known, and otherwise "There is no
-  ;; information available about its arguments." or "It has no
-  ;; arguments." (And why is %SIMPLE-FUN-ARGLIST a string instead of a
-  ;; list of symbols anyway?)
   (let ((args (%simple-fun-arglist x)))
   (let ((args (%simple-fun-arglist x)))
-    (format s "~@:_~@(~@[~A ~]arguments:~@:_~)" kind)
     (cond ((not args)
     (cond ((not args)
-          (format s "  There is no argument information available."))
-         ((string= args "()")
           (write-string "  There are no arguments." s))
          (t
           (write-string "  There are no arguments." s))
          (t
+           (format s "~&~@(The ~@[~A's ~]arguments are:~@:_~)" kind)
           (write-string "  " s)
           (write-string "  " s)
-          (pprint-logical-block (s nil)
-            (pprint-indent :current 2)
-            (write-string args s)))))
+            (let ((*print-pretty* t)
+                  (*print-escape* t)
+                  (*print-base* 10)
+                  (*print-radix* nil))
+              (pprint-logical-block (s nil)
+                 (pprint-indent :current 2)
+                 (format s "~A" args))))))
   (let ((name (or name (%simple-fun-name x))))
     (%describe-doc name s 'function kind)
     (unless (eq kind :macro)
   (let ((name (or name (%simple-fun-name x))))
     (%describe-doc name s 'function kind)
     (unless (eq kind :macro)
-      (%describe-function-name name s (%simple-fun-type x))))
+      (%describe-fun-name name s (%simple-fun-type x))))
   (%describe-compiled-from (sb-kernel:fun-code-header x) s))
 
   (%describe-compiled-from (sb-kernel:fun-code-header x) s))
 
-;;; Describe a function with the specified kind and name. The latter
-;;; arguments provide some information about where the function came
-;;; from. KIND=NIL means not from a name.
-(defun %describe-function (x s &optional (kind nil) name)
+;;; Describe a function object. KIND and NAME provide some information
+;;; about where the function came from.
+(defun %describe-fun (x s &optional (kind :function) (name nil))
   (declare (type function x))
   (declare (type stream s))
   (declare (type function x))
   (declare (type stream s))
-  (declare (type (member :macro :function nil) kind))
+  (declare (type (member :macro :function) kind))
   (fresh-line s)
   (fresh-line s)
-  (ecase kind
-    (:macro (format s "Macro-function: ~S" x))
-    (:function (format s "Function: ~S" x))
-    ((nil) (format s "~S is a function." x)))
-  (case (get-type x)
-    (#.sb-vm:closure-header-widetag
-     (%describe-function-compiled (%closure-fun x) s kind name)
-     (format s "~@:_Its closure environment is:")
-     (pprint-logical-block (s nil)
-       (pprint-indent :current 8)
-       (dotimes (i (- (get-closure-length x) (1- sb-vm:closure-info-offset)))
-        (format s "~@:_~S: ~S" i (%closure-index-ref x i)))))
-    ((#.sb-vm:simple-fun-header-widetag #.sb-vm:closure-fun-header-widetag)
-     (%describe-function-compiled x s kind name))
-    (#.sb-vm:funcallable-instance-header-widetag
-     (typecase x
-       (standard-generic-function
-       ;; There should be a special method for this case; we'll
-       ;; delegate to that.
-       (describe-object x s))
-       (t
-       (format s "~@:_It is an unknown type of funcallable instance."))))
-    (t
-     (format s "~@:_It is an unknown type of function."))))
+  (pprint-logical-block (s nil)
+    (ecase kind
+      (:macro (format s "Macro-function: ~S" x))
+      (:function (if name
+                    (format s "Function: ~S" x)
+                    (format s "~S is a function." x))))
+    (format s "~@:_~@<Its associated name (as in ~S) is ~2I~_~S.~:>"
+           'function-lambda-expression
+           (%fun-name x))
+    (case (widetag-of x)
+      (#.sb-vm:closure-header-widetag
+       (%describe-fun-compiled (%closure-fun x) s kind name)
+       (format s "~@:_Its closure environment is:")
+       (pprint-logical-block (s nil)
+        (pprint-indent :current 8)
+        (dotimes (i (- (get-closure-length x) (1- sb-vm:closure-info-offset)))
+          (format s "~@:_~S: ~S" i (%closure-index-ref x i)))))
+      (#.sb-vm:simple-fun-header-widetag
+       (%describe-fun-compiled x s kind name))
+      (#.sb-vm:funcallable-instance-header-widetag
+       ;; Only STANDARD-GENERIC-FUNCTION would be handled here, but
+       ;; since it has its own DESCRIBE-OBJECT method, it should've been
+       ;; picked off before getting here. So hopefully we never get here.
+       (format s "~@:_It is an unknown type of funcallable instance."))
+      (t
+       (format s "~@:_It is an unknown type of function."))))
+  (terpri s))
 
 (defmethod describe-object ((x function) s)
 
 (defmethod describe-object ((x function) s)
-  (%describe-function x s))
-  
+  (%describe-fun x s :function))
+
+(defgeneric describe-symbol-fdefinition (function stream &key name))
+
+(defmethod describe-symbol-fdefinition ((fun function) stream &key name)
+  (%describe-fun fun stream :function name))
+
+(defmethod describe-symbol-fdefinition ((fun standard-generic-function) stream
+                                        &key name)
+  (declare (ignore name))
+  ;; Just delegate.
+  (describe-object fun stream))
+
 (defmethod describe-object ((x symbol) s)
   (declare (type stream s))
 
 (defmethod describe-object ((x symbol) s)
   (declare (type stream s))
 
        (multiple-value-bind (symbol status)
            (find-symbol (symbol-name x) package)
          (declare (ignore symbol))
        (multiple-value-bind (symbol status)
            (find-symbol (symbol-name x) package)
          (declare (ignore symbol))
-         (format s "~S is ~_an ~(~A~) symbol ~_in ~S."
+         (format s "~&~@<~S is ~_an ~(~A~) symbol ~_in ~S.~:>"
                  x status (symbol-package x)))
                  x status (symbol-package x)))
-       (format s "~S is ~_an uninterned symbol." x)))
+       (format s "~&~@<~S is ~_an uninterned symbol.~:>" x)))
   ;; TO DO: We could grovel over all packages looking for and
   ;; reporting other phenomena, e.g. IMPORT and SHADOW, or
   ;; availability in some package even after (SYMBOL-PACKAGE X) has
   ;; TO DO: We could grovel over all packages looking for and
   ;; reporting other phenomena, e.g. IMPORT and SHADOW, or
   ;; availability in some package even after (SYMBOL-PACKAGE X) has
   (let* ((kind (info :variable :kind x))
         (wot (ecase kind
                (:special "special variable")
   (let* ((kind (info :variable :kind x))
         (wot (ecase kind
                (:special "special variable")
+               (:macro "symbol macro")
                (:constant "constant")
                (:global "undefined variable")
                (:alien nil))))
                (:constant "constant")
                (:global "undefined variable")
                (:alien nil))))
-    (cond
-     ((eq kind :alien)
-      (let ((info (info :variable :alien-info x)))
-       (format s "~@:_~@<It is an alien at #X~8,'0X of type ~3I~:_~S.~:>~@:_"
-               (sap-int (eval (sb-alien::heap-alien-info-sap-form info)))
-               (sb-alien-internals:unparse-alien-type
-                (sb-alien::heap-alien-info-type info)))
-       (format s "~@<Its current value is ~3I~:_~S.~:>"
-               (eval x))))
-     ((boundp x)
-      (format s "~@:_It is a ~A; its ~_value is ~S." wot (symbol-value x)))
-     ((not (eq kind :global))
-      (format s "~@:_It is a ~A; no current value." wot)))
+    (pprint-logical-block (s nil)
+      (cond
+       ((eq kind :alien)
+       (let ((info (info :variable :alien-info x)))
+         (format s "~&~@<It is an alien at #X~8,'0X of type ~3I~:_~S.~:>"
+                 (sap-int (eval (sb-alien::heap-alien-info-sap-form info)))
+                 (sb-alien-internals:unparse-alien-type
+                  (sb-alien::heap-alien-info-type info)))
+         (format s "~&~@<Its current value is ~3I~:_~S.~:>"
+                 (eval x))))
+       ((eq kind :macro)
+       (let ((expansion (info :variable :macro-expansion x)))
+         (format s "~&It is a ~A with expansion ~S." wot expansion)))
+       ((boundp x)
+       (format s "~&~@<It is a ~A; its ~_value is ~S.~:>"
+               wot (symbol-value x)))
+       ((not (eq kind :global))
+       (format s "~&~@<It is a ~A; no current value.~:>" wot)))
 
 
-    (when (eq (info :variable :where-from x) :declared)
-      (format s "~@:_Its declared type ~_is ~S."
-             (type-specifier (info :variable :type x))))
+      (when (eq (info :variable :where-from x) :declared)
+       (format s "~&~@<Its declared type ~_is ~S.~:>"
+               (type-specifier (info :variable :type x)))))
 
     (%describe-doc x s 'variable kind))
 
   ;; Print out properties.
 
     (%describe-doc x s 'variable kind))
 
   ;; Print out properties.
-  (format s "~@[~@:_Its SYMBOL-PLIST is ~@<~2I~_~S~:>.~]" (symbol-plist x))
+  (format s "~@[~&Its SYMBOL-PLIST is ~@<~2I~_~S~:>.~]" (symbol-plist x))
 
   ;; Describe the function cell.
   (cond ((macro-function x)
 
   ;; Describe the function cell.
   (cond ((macro-function x)
-        (%describe-function (macro-function x) s :macro x))
+        (%describe-fun (macro-function x) s :macro x))
        ((special-operator-p x)
        ((special-operator-p x)
-        (%describe-doc x s 'function "Special form"))
+        (%describe-doc x s :function "Special form"))
        ((fboundp x)
        ((fboundp x)
-        (%describe-function (fdefinition x) s :function x)))
-
-  ;; FIXME: Print out other stuff from the INFO database:
-  ;;   * Does it name a type?
-  ;;   * Is it a structure accessor? (This is important since those are 
-  ;;     magical in some ways, e.g. blasting the structure if you 
-  ;;     redefine them.)
+        (describe-symbol-fdefinition (fdefinition x) s :name x)))
 
   ;; Print other documentation.
   (%describe-doc x s 'structure "Structure")
   (%describe-doc x s 'type "Type")
   (%describe-doc x s 'setf "Setf macro")
 
   ;; Print other documentation.
   (%describe-doc x s 'structure "Structure")
   (%describe-doc x s 'type "Type")
   (%describe-doc x s 'setf "Setf macro")
-
   (dolist (assoc (info :random-documentation :stuff x))
     (format s
   (dolist (assoc (info :random-documentation :stuff x))
     (format s
-           "~@:_Documentation on the ~(~A~):~@:_~A"
+           "~&~@<Documentation on the ~(~A~):~@:_~A~:>"
            (car assoc)
            (cdr assoc)))
   
            (car assoc)
            (cdr assoc)))
   
-  ;; Describe the associated class, if any.
-  (let ((symbol-named-class (cl:find-class x nil)))
+  ;; Mention the associated type information, if any.
+  ;;
+  ;; As of sbcl-0.7.2, (INFO :TYPE :KIND X) might be
+  ;;   * :PRIMITIVE, which is handled by the FIND-CLASS case.
+  ;;   * :DEFINED, which is handled specially.
+  ;;   * :INSTANCE, which is handled by the FIND-CLASS case.
+  ;;   * :FORTHCOMING-DEFCLASS-TYPE, which is an internal-to-the-compiler
+  ;;     note that we don't try to report.
+  ;;   * NIL, in which case there's nothing to see here, move along.
+  (when (eq (info :type :kind x) :defined)
+    (format s "~&It names a type specifier."))
+  (let ((symbol-named-class (find-class x nil)))
     (when symbol-named-class
     (when symbol-named-class
-      (format t "~&It names a class ~A." symbol-named-class)
-      (describe symbol-named-class))))
+      (format s "~&It names a class ~A." symbol-named-class)
+      (describe symbol-named-class s)))
+
+  (terpri s))