;;; -*-  Mode: Lisp; Package: Maxima; Syntax: Common-Lisp; Base: 10 -*- ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;     The data in this file contains enhancements.                   ;;;;;
;;;                                                                    ;;;;;
;;;  Copyright (c) 1984,1987 by William Schelter,University of Texas   ;;;;;
;;;     All rights reserved                                            ;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; gjc: 6:27pm  sunday, 20 july 1980
;;;       (c) copyright 1979 massachusetts institute of technology       ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(in-package :maxima)

(macsyma-module trmode)

(defmvar $mode_checkp t "if true, modedeclare checks the modes of bound variables.")
(defmvar $mode_check_warnp t "if true, mode errors are described.")
(defmvar $mode_check_errorp nil "if true, modedeclare calls error.")

(defun mseemingly-unbound (x)
  (or (not (boundp x)) (eq (symbol-value x) x)))

(defun assign-mode-check (var value)
  (let ((mode (tr-get-mode var))
	(user-level ($get var '$value_check)))
    (when mode
      (let (($mode_check_warnp t)
	    ($mode_check_errorp t))
	(chekvalue var mode value)))
    (when user-level
      (mcall user-level value)))
  value)

(def%tr $define_variable (form)	;;VAR INIT MODE.
  (cond ((> (length form) 3)
	 (destructuring-let (((var val mode) (cdr form)))
			    (let ((mode-form `(($modedeclare) ,var ,mode)))
			      (translate mode-form)
			      (push-pre-transl-form
			       ;; POSSIBLE OVERKILL HERE
			       `(declaim (special ,var)))
			      (push var defined_variables)
			      ;; Get rid of previous definitions put on by
			      ;; the translator.
			      (do ((l *pre-transl-forms* (cdr l)))
				  ((null l))
				;; REMOVE SOME OVERKILL
				(cond ((and (eq (caar l) 'def-mtrvar)
					    (eq (cadar l) var))
				       (setq *pre-transl-forms* (delete (car l) *pre-transl-forms* :test #'eq)))))
			      (if (not (eq mode '$any))
				  ;; so that the rest of the translation gronks this.
				  (putprop var 'assign-mode-check 'assign))
			      `($any . (eval-when
					   (:compile-toplevel :load-toplevel :execute)
					   (meval* ',mode-form)
					   ,(if (not (eq mode '$any))
						`(defprop ,var assign-mode-check assign))
					   (def-mtrvar ,var ,(dtranslate val)))))))
	(t
	 (tr-format (intl:gettext "error: 'define_variable' must have 3 arguments; found: ~:M~%") form)
	 nil)))

;; the priority fails when a DEF-MTRVAR is done, then the user
;; sets the variable, because the set-priority stays the same.
;; This causes some Define_Variable's to over-ride the user setting,
;; but only in the case of re-loading, what we were worried about
;; is pre-setting of variables of autoloading files.

(defmspec $define_variable  (l)
  (setq l (cdr l))
  (unless (> (length l) 2)
    (merror (intl:gettext "define_variable: expected three arguments; found: ~M") `((mlist) ,@l)))
  (unless (symbolp (car l))
    (merror (intl:gettext "define_variable: first argument must be a symbol; found: ~M") (car l)))
  (meval `(($modedeclare) ,(car l) ,(caddr l)))
  (unless (eq (caddr l) '$any)
    (putprop (car l) 'assign-mode-check 'assign))
  (if (mseemingly-unbound (car l))
      (meval `((msetq) ,(car l) ,(cadr l)))
      (meval (car l))))


(defmspec $mode_identity (l)
  (setq l (cdr l))
  (unless (= (length l) 2)
    (merror (intl:gettext "mode_identity: expected two arguments; found: ~M") `((mlist) ,@l)))
  (let* ((obj (cadr l))
	 (v (meval obj)))
    (chekvalue obj (ir-or-extend (car l)) v)
    v))

(def%tr $mode_identity (form)
  `(,(ir-or-extend (cadr form)) . ,(dtranslate (caddr form))))

(defun ir-or-extend (x)
  (let ((built-in-type (case x
			 (($float $real $floatp $flonum $floatnum) '$float)
			 (($fixp $fixnum $integer) '$fixnum)
			 (($rational $rat) '$rational)
			 (($number $bignum $big) '$number)
			 (($boolean $bool) '$boolean)
			 (($list $listp) '$list)
			 ($complex '$complex)
			 (($any $none $any_check) '$any))))
    (if built-in-type built-in-type
	(prog1
	    x
	  (mtell (intl:gettext "modedeclare: ~M is not a built-in type; assuming it is a Maxima extension type.") x)))))

(def%tr $modedeclare (form)
  (do ((l (cdr form) (cddr l)))
      ((null l))
    (declmode (car l) (ir-or-extend (cadr l)) t)))

(defun ass-eq-ref (table key &optional dflt)
  (let ((val (assoc key table :test #'eq)))
    (if val
	(cdr val)
	dflt)))

(defun ass-eq-set (val table key)
  (let ((cell (assoc key table :test #'eq)))
    (if cell
	(setf (cdr cell) val)
	(push (cons key val) table)))
  table)


;;; Possible calls to MODEDECLARE.
;;; MODEDECLARE(<oblist>,<mode>,<oblist>,<mode>,...)
;;; where <oblist> is:
;;; an ATOM, signifying a VARIABLE.
;;; a LIST, giving a list of objects of <mode>
;;;

(defmspec $modedeclare (x)
  (setq x (cdr x))
  (when (oddp (length x))
    (merror (intl:gettext "mode_declare: expected an even number of arguments; found: ~M") `((mlist) ,@x)))
  (do ((l x (cddr l)) (nl))
      ((null l) (cons '(mlist) (nreverse nl)))
    (declmode (car l) (ir-or-extend (cadr l)) nil)
    (push (car l) nl)))

(defun tr-declare-varmode (variable mode)
  (declvalue variable (ir-or-extend mode) t))

;;; If TRFLAG is TRUE, we are in the translator, if NIL, we are in the
;;; interpreter.

(defun declmode (form mode trflag)
  (cond ((atom form)
	 (declvalue form mode trflag)
	 (and (not trflag) $mode_checkp (chekvalue form mode)))
	((eq 'mlist (caar form))
	 (mapc #'(lambda (l) (declmode l mode trflag)) (cdr form)))
	((member 'array (cdar form) :test #'eq)
	 (declarray (caar form) mode))
	((eq '$function (caar form))
	 (mapc
       #'(lambda (l)
           (if (stringp l) (setq l ($verbify l)))
           (declfun l mode))
       (cdr form)))
	((member (caar form) '($fixed_num_args_function $variable_num_args_function) :test #'eq)
	 (mapc
       #'(lambda (f)
           (if (stringp f) (setq f ($verbify f)))
           (declfun f mode)
           (mputprop f t (caar form)))
       (cdr form)))
	((eq '$completearray (caar form))
	 (mapc #'(lambda (l)
		   (putprop (if (atom l) l (caar l)) mode 'array-mode))
	       (cdr form)))
	((eq '$array (caar form))
	 (mapc #'(lambda (l) (mputprop l mode 'array-mode)) (cdr form)))
	((eq '$arrayfun (caar form))
	 (mapc #'(lambda (l) (mputprop l mode 'arrayfun-mode)) (cdr form)))
	(t
	 (declfun (caar form) mode))))

(defun declvalue (v mode trflag)
  (when trflag (setq v (teval v)))
  (add2lnc v $props)
  (setf (tr-get-mode v) mode))

(defun chekvalue (my-v mode &optional (val (meval1 my-v) val-givenp))
  (cond ((or val-givenp (not (eq my-v val)))
	 ;; hack because macsyma PROG binds variable to itself. 
	 (let ((checker (assoc mode `(($float . floatp)
				      ($fixnum . integerp)
				      ($number . numberp)
				      ($list . $listp)
				      ($boolean . ,#'(lambda (u) (member u '(t nil) :test #'eq))))
			       :test #'eq))
	       (nchecker (assoc mode '(($float . $real)
				       ($fixnum . $integer)
				       ($complex . $complex))
				:test #'eq))
	       (not-done t))
	   (if (cond ((and checker (not (funcall (cdr checker) val))
			   (if nchecker
			       (prog1
				   (not (mfuncall '$featurep val (cdr nchecker)))
				 (setq not-done nil))
			       t)))
		     ((if not-done (and nchecker (not (mfuncall '$featurep val (cdr nchecker)))))))
	       (signal-mode-error my-v mode val))))))

(defun signal-mode-error (object mode value)
  (cond ((and $mode_check_warnp (not $mode_check_errorp))
	 (mtell (intl:gettext "translator: ~:M was declared with mode ~:M, but it has value: ~M") object mode value))
	($mode_check_errorp
	 (merror (intl:gettext "translator: ~:M was declared with mode ~:M, but it has value: ~M") object mode value))))
			  
(defun put-mode (name mode type)
  (if (get name 'tbind)
      (setf (tr-get-val-modes name) (ass-eq-set mode (tr-get-val-modes name) type))
      (setf (get name type) mode)))

(defun declarray (ar mode)
  (put-mode ar mode 'array-mode))

(defun declfun (f mode)
  (put-mode f mode 'function-mode))

;;; 1/2 is not $RATIONAL. bad name. it means CRE form.

(defun udm-err (mode)
  (mtell (intl:gettext "translator: no such mode declaration: ~:M~%") mode))
