;;;; COERCE and related code

;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; This software is derived from the CMU CL system, which was
;;;; written at Carnegie Mellon University and released into the
;;;; public domain. The software is in the public domain and is
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
;;;; files for more information.

(in-package "SB!IMPL")

(macrolet ((def (name result access src-type &optional typep)
             `(defun ,name (object ,@(if typep '(type) ()))
                (declare (type ,(ecase src-type
                                       (:list 'list)
                                       (:vector 'vector)
                                       (:sequence 'sequence)) object))
                (do* ((index 0 (1+ index))
                      (length (length object))
                      (result ,result)
                      (in-object object))
                     ((>= index length) result)
                  (declare (fixnum length index))
                  (declare (type vector result))
                  (setf (,access result index)
                        ,(ecase src-type
                           (:list '(pop in-object))
                           (:vector '(aref in-object index))
                           (:sequence '(elt in-object index))))))))

  (def list-to-vector* (make-sequence type length)
    aref :list t)

  (def vector-to-vector* (make-sequence type length)
    aref :vector t)

  (def sequence-to-vector* (make-sequence type length)
    aref :sequence t))

(defun vector-to-list* (object)
  (declare (type vector object))
  (let ((result (list nil))
        (length (length object)))
    (declare (fixnum length))
    (do ((index 0 (1+ index))
         (splice result (cdr splice)))
        ((>= index length) (cdr result))
      (declare (fixnum index))
      (rplacd splice (list (aref object index))))))

(defvar *offending-datum*); FIXME: Remove after debugging COERCE.

;;; These are used both by the full DEFUN function and by various
;;; optimization transforms in the constant-OUTPUT-TYPE-SPEC case.
;;;
;;; Most of them are INLINE so that they can be optimized when the
;;; argument type is known. It might be better to do this with
;;; DEFTRANSFORMs, though.
(declaim (inline coerce-to-list))
(declaim (inline coerce-to-vector))
(defun coerce-to-fun (object)
  ;; (Unlike the other COERCE-TO-FOOs, this one isn't inline, because
  ;; it's so big and because optimizing away the outer ETYPECASE
  ;; doesn't seem to buy us that much anyway.)
  (etypecase object
    (symbol
     ;; ANSI lets us return ordinary errors (non-TYPE-ERRORs) here.
     (cond ((macro-function object)
            (error "~S names a macro." object))
           ((special-operator-p object)
            (error "~S is a special operator." object))
           (t (fdefinition object))))
    (list
     (case (first object)
       ((setf)
        (fdefinition object))
       ((lambda)
        ;; FIXME: If we go to a compiler-only implementation, this can
        ;; become COMPILE instead of EVAL, which seems nicer to me.
        (eval `(function ,object)))
       (t
        (error 'simple-type-error
               :datum object
               :expected-type '(or symbol
                                   ;; KLUDGE: ANSI wants us to
                                   ;; return a TYPE-ERROR here, and
                                   ;; a TYPE-ERROR is supposed to
                                   ;; describe the expected type,
                                   ;; but it's not obvious how to
                                   ;; describe the coerceable cons
                                   ;; types, so we punt and just say
                                   ;; CONS. -- WHN 20000503
                                   cons)
               :format-control "~S can't be coerced to a function."
               :format-arguments (list object)))))))

(defun coerce-to-list (object)
  (etypecase object
    (vector (vector-to-list* object))))

(defun coerce-to-vector (object output-type-spec)
  (etypecase object
    (list (list-to-vector* object output-type-spec))
    (vector (vector-to-vector* object output-type-spec))))

;;; old working version
(defun coerce (object output-type-spec)
  #!+sb-doc
  "Coerce the Object to an object of type Output-Type-Spec."
  (flet ((coerce-error ()
           (/show0 "entering COERCE-ERROR")
           (error 'simple-type-error
                  :format-control "~S can't be converted to type ~S."
                  :format-arguments (list object output-type-spec)
                  :datum object
                  :expected-type output-type-spec)))
    (let ((type (specifier-type output-type-spec)))
      (cond
        ((%typep object output-type-spec)
         object)
        ((eq type *empty-type*)
         (coerce-error))
        ((type= type (specifier-type 'character))
         (character object))
        ((numberp object)
         (cond
           ((csubtypep type (specifier-type 'single-float))
            (let ((res (%single-float object)))
              (unless (typep res output-type-spec)
                (coerce-error))
              res))
           ((csubtypep type (specifier-type 'double-float))
            (let ((res (%double-float object)))
              (unless (typep res output-type-spec)
                (coerce-error))
              res))
           #!+long-float
           ((csubtypep type (specifier-type 'long-float))
            (let ((res (%long-float object)))
              (unless (typep res output-type-spec)
                (coerce-error))
              res))
           ((csubtypep type (specifier-type 'float))
            (let ((res (%single-float object)))
              (unless (typep res output-type-spec)
                (coerce-error))
              res))
           (t
            (let ((res
                   (cond
                     ((csubtypep type (specifier-type '(complex single-float)))
                      (complex (%single-float (realpart object))
                               (%single-float (imagpart object))))
                     ((csubtypep type (specifier-type '(complex double-float)))
                      (complex (%double-float (realpart object))
                               (%double-float (imagpart object))))
                     #!+long-float
                     ((csubtypep type (specifier-type '(complex long-float)))
                      (complex (%long-float (realpart object))
                               (%long-float (imagpart object))))
                     ((csubtypep type (specifier-type '(complex float)))
                      (complex (%single-float (realpart object))
                               (%single-float (imagpart object))))
                     ((and (typep object 'rational)
                           (csubtypep type (specifier-type '(complex float))))
                      ;; Perhaps somewhat surprisingly, ANSI specifies
                      ;; that (COERCE FOO 'FLOAT) is a SINGLE-FLOAT,
                      ;; not dispatching on
                      ;; *READ-DEFAULT-FLOAT-FORMAT*.  By analogy, we
                      ;; do the same for complex numbers. -- CSR,
                      ;; 2002-08-06
                      (complex (%single-float object)))
                     ((csubtypep type (specifier-type 'complex))
                      (complex object))
                     (t
                      (coerce-error)))))
              ;; If RES has the wrong type, that means that rule of
              ;; canonical representation for complex rationals was
              ;; invoked. According to the Hyperspec, (coerce 7/2
              ;; 'complex) returns 7/2. Thus, if the object was a
              ;; rational, there is no error here.
              (unless (or (typep res output-type-spec)
                          (rationalp object))
                (coerce-error))
              res))))
        ((csubtypep type (specifier-type 'list))
         (if (vectorp object)
             (cond
               ((type= type (specifier-type 'list))
                (vector-to-list* object))
               ((type= type (specifier-type 'null))
                (if (= (length object) 0)
                    'nil
                    (sequence-type-length-mismatch-error type
                                                         (length object))))
               ((cons-type-p type)
                (multiple-value-bind (min exactp)
                    (sb!kernel::cons-type-length-info type)
                  (let ((length (length object)))
                    (if exactp
                        (unless (= length min)
                          (sequence-type-length-mismatch-error type length))
                        (unless (>= length min)
                          (sequence-type-length-mismatch-error type length)))
                    (vector-to-list* object))))
               (t (sequence-type-too-hairy (type-specifier type))))
             (if (sequencep object)
                 (cond
                   ((type= type (specifier-type 'list))
                    (sb!sequence:make-sequence-like
                     nil (length object) :initial-contents object))
                   ((type= type (specifier-type 'null))
                    (if (= (length object) 0)
                        'nil
                        (sequence-type-length-mismatch-error type
                                                             (length object))))
                   ((cons-type-p type)
                    (multiple-value-bind (min exactp)
                        (sb!kernel::cons-type-length-info type)
                      (let ((length (length object)))
                        (if exactp
                            (unless (= length min)
                              (sequence-type-length-mismatch-error type length))
                            (unless (>= length min)
                              (sequence-type-length-mismatch-error type length)))
                        (sb!sequence:make-sequence-like
                         nil length :initial-contents object))))
                   (t (sequence-type-too-hairy (type-specifier type))))
                 (coerce-error))))
        ((csubtypep type (specifier-type 'vector))
         (typecase object
           ;; FOO-TO-VECTOR* go through MAKE-SEQUENCE, so length
           ;; errors are caught there. -- CSR, 2002-10-18
           (list (list-to-vector* object output-type-spec))
           (vector (vector-to-vector* object output-type-spec))
           (sequence (sequence-to-vector* object output-type-spec))
           (t
            (coerce-error))))
        ((and (csubtypep type (specifier-type 'sequence))
              (find-class output-type-spec nil))
         (let ((class (find-class output-type-spec)))
           (unless (sb!mop:class-finalized-p class)
             (sb!mop:finalize-inheritance class))
           (sb!sequence:make-sequence-like
            (sb!mop:class-prototype class)
            (length object) :initial-contents object)))
        ((csubtypep type (specifier-type 'function))
         (when (and (legal-fun-name-p object)
                    (not (fboundp object)))
           (error 'simple-type-error
                  :datum object
                  ;; FIXME: SATISFIES FBOUNDP is a kinda bizarre broken
                  ;; type specifier, since the set of values it describes
                  ;; isn't in general constant in time. Maybe we could
                  ;; find a better way of expressing this error? (Maybe
                  ;; with the UNDEFINED-FUNCTION condition?)
                  :expected-type '(satisfies fboundp)
               :format-control "~S isn't fbound."
               :format-arguments (list object)))
         (when (and (symbolp object)
                    (sb!xc:macro-function object))
           (error 'simple-type-error
                  :datum object
                  :expected-type '(not (satisfies sb!xc:macro-function))
                  :format-control "~S is a macro."
                  :format-arguments (list object)))
         (when (and (symbolp object)
                    (special-operator-p object))
           (error 'simple-type-error
                  :datum object
                  :expected-type '(not (satisfies special-operator-p))
                  :format-control "~S is a special operator."
                  :format-arguments (list object)))
         (eval `#',object))
        (t
         (coerce-error))))))

;;; new version, which seems as though it should be better, but which
;;; does not yet work
#+nil
(defun coerce (object output-type-spec)
  #!+sb-doc
  "Coerces the Object to an object of type Output-Type-Spec."
  (flet ((coerce-error ()
           (error 'simple-type-error
                  :format-control "~S can't be converted to type ~S."
                  :format-arguments (list object output-type-spec)))
         (check-result (result)
           #!+high-security (aver (typep result output-type-spec))
           result))
    (let ((type (specifier-type output-type-spec)))
      (cond
        ((%typep object output-type-spec)
         object)
        ((eq type *empty-type*)
         (coerce-error))
        ((csubtypep type (specifier-type 'character))
         (character object))
        ((csubtypep type (specifier-type 'function))
         (coerce-to-fun object))
        ((numberp object)
         (let ((res
                (cond
                  ((csubtypep type (specifier-type 'single-float))
                   (%single-float object))
                  ((csubtypep type (specifier-type 'double-float))
                   (%double-float object))
                  #!+long-float
                  ((csubtypep type (specifier-type 'long-float))
                   (%long-float object))
                  ((csubtypep type (specifier-type 'float))
                   (%single-float object))
                  ((csubtypep type (specifier-type '(complex single-float)))
                   (complex (%single-float (realpart object))
                            (%single-float (imagpart object))))
                  ((csubtypep type (specifier-type '(complex double-float)))
                   (complex (%double-float (realpart object))
                            (%double-float (imagpart object))))
                  #!+long-float
                  ((csubtypep type (specifier-type '(complex long-float)))
                   (complex (%long-float (realpart object))
                            (%long-float (imagpart object))))
                  ((csubtypep type (specifier-type 'complex))
                   (complex object))
                  (t
                   (coerce-error)))))
           ;; If RES has the wrong type, that means that rule of
           ;; canonical representation for complex rationals was
           ;; invoked. According to the ANSI spec, (COERCE 7/2
           ;; 'COMPLEX) returns 7/2. Thus, if the object was a
           ;; rational, there is no error here.
           (unless (or (typep res output-type-spec) (rationalp object))
             (coerce-error))
           res))
        ((csubtypep type (specifier-type 'list))
         (coerce-to-list object))
        ((csubtypep type (specifier-type 'string))
         (check-result (coerce-to-simple-string object)))
        ((csubtypep type (specifier-type 'bit-vector))
         (check-result (coerce-to-bit-vector object)))
        ((csubtypep type (specifier-type 'vector))
         (check-result (coerce-to-vector object output-type-spec)))
        (t
         (coerce-error))))))
