;;; proclaim.lisp
;;;
;;; Copyright (C) 2003-2006 Peter Graves
;;; $Id$
;;;
;;; This program is free software; you can redistribute it and/or
;;; modify it under the terms of the GNU General Public License
;;; as published by the Free Software Foundation; either version 2
;;; of the License, or (at your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program; if not, write to the Free Software
;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
;;;
;;; As a special exception, the copyright holders of this library give you
;;; permission to link this library with independent modules to produce an
;;; executable, regardless of the license terms of these independent
;;; modules, and to copy and distribute the resulting executable under
;;; terms of your choice, provided that you also meet, for each linked
;;; independent module, the terms and conditions of the license of that
;;; module.  An independent module is a module which is not derived from
;;; or based on this library.  If you modify this library, you may extend
;;; this exception to your version of the library, but you are not
;;; obligated to do so.  If you do not wish to do so, delete this
;;; exception statement from your version.

(in-package #:system)

(export '(check-declaration-type proclaimed-type proclaimed-ftype ftype-result-type *inline-declarations*))

(defmacro declaim (&rest decls)
`(eval-when (:compile-toplevel :load-toplevel :execute)
   ,@(mapcar (lambda (decl) `(proclaim ',decl))
             decls)))

(defun declaration-error (name)
  (error 'simple-error
         :format-control "The symbol ~S cannot be both the name of a type and the name of a declaration."
         :format-arguments (list name)))

(defvar *inline-declarations* nil)
(defvar *declaration-types* (make-hash-table :test 'eq))

;; "A symbol cannot be both the name of a type and the name of a declaration.
;; Defining a symbol as the name of a class, structure, condition, or type,
;; when the symbol has been declared as a declaration name, or vice versa,
;; signals an error."
(defun check-declaration-type (name)
  (when (gethash1 name (the hash-table *declaration-types*))
    (declaration-error name)))

(defun proclaim (declaration-specifier)
  (unless (symbolp (car declaration-specifier))
    (%type-error (car declaration-specifier) 'symbol))
  ;; (cdr declaration-specifier) must be a proper list.
  (unless (listp (cddr declaration-specifier))
    (%type-error (cddr declaration-specifier) 'list))
  (case (car declaration-specifier)
    (SPECIAL
     (dolist (name (cdr declaration-specifier))
       (%defvar name)))
    (OPTIMIZE
     (dolist (spec (cdr declaration-specifier))
       (let ((val 3)
             (quality spec))
         (when (consp spec)
           (setf quality (%car spec)
                 val (cadr spec)))
         (when (and (fixnump val)
                    (<= 0 val 3))
           (case quality
             (SPEED
              (setf *speed* val))
             (SPACE
              (setf *space* val))
             (SAFETY
              (setf *safety* val))
             (DEBUG
              (setf *debug* val)))))))
    (FTYPE
     (unless (cdr declaration-specifier)
       (error "No type specified in FTYPE declaration: ~S" declaration-specifier))
     (apply 'proclaim-ftype (cdr declaration-specifier)))
    (TYPE
     (unless (cdr declaration-specifier)
       (error "No type specified in TYPE declaration: ~S" declaration-specifier))
     (apply 'proclaim-type (cdr declaration-specifier)))
    ((INLINE NOTINLINE)
     (dolist (name (cdr declaration-specifier))
       (if (symbolp name)
         (setf (get name '%inline) (car declaration-specifier))
         (push (cons name (car declaration-specifier)) *inline-declarations*))))
    (DECLARATION
     (dolist (name (cdr declaration-specifier))
       (when (or (get name 'deftype-definition)
                 (find-class name nil))
         (declaration-error name))
       (setf (gethash name (the hash-table *declaration-types*)) name)))
    (:explain
     (dolist (spec (cdr declaration-specifier))
       (let ((val t)
             (quality spec))
         (when (consp spec)
           (setf quality (%car spec))
           (when (= (length spec) 2)
             (setf val (%cadr spec))))
         (if val
             (pushnew quality *explain*)
             (setf *explain* (remove quality *explain*))))))))

(defun proclaim-type (type &rest names)
  (dolist (name names)
    (setf (get name 'proclaimed-type) type)))

(defun proclaimed-type (name)
  (get name 'proclaimed-type))

(declaim (type hash-table *proclaimed-ftypes*))
(defconst *proclaimed-ftypes* (make-hash-table :test 'equal))

(declaim (inline proclaim-ftype-1))
(defun proclaim-ftype-1 (ftype name)
  (declare (optimize speed))
  (if (symbolp name)
      (setf (get name 'proclaimed-ftype) ftype)
      (setf (gethash name *proclaimed-ftypes*) ftype)))
(declaim (notinline proclaim-ftype-1))

(defun proclaim-ftype (ftype &rest names)
  (declare (optimize speed))
  (declare (inline proclaim-ftype-1))
  (dolist (name names)
    (proclaim-ftype-1 ftype name)))

(defun proclaimed-ftype (name)
  (if (symbolp name)
      (get name 'proclaimed-ftype)
      (gethash1 name *proclaimed-ftypes*)))

(defun ftype-result-type (ftype)
  (if (atom ftype)
      '*
      (let ((result-type (third ftype)))
        (if result-type
            result-type
            '*))))
