;;; from maxima-5.9.0/maxima-5.9.0/src/mlisp.lisp

(in-package :maxima)

;;; changes, 8/14/05
;;; 1. Improved error messages for mset
;;; 2. Allows setting of record fields e.g.  XX@YY:45 if mset_extension_operators set up

;;; author Richard Fateman

;; modifications 2005/08/28 Robert Dodier
;; (0) cut matrix assignment code from previous rev (but kept parallel multiple assignment)
;; (1) $NEW barfs if #arguments != 1, or argument has no defstruct, or wrong number of initializers.
;; (2) $DEFSTRUCT allows 1 or more arguments, returns a list of defstructs.
;; (3) use $PUT and $GET to maintain defstruct properties 
;;     (renamed to $DEFSTRUCT_DEFAULT and $DEFSTRUCT_TEMPLATE).
;;     This makes defstruct properties visible to user via get and propvars.
;;     Also, this makes `kill' kill defstructs.

;; If this is the last def'n in file of mset_extension_operators,
;; it will disable the $@ defstruct features

(defparameter mset_extension_operators nil)

(defmfun mset (x y)
  (prog ()
    ;; first see if we are supposed to report this assignment
    ;; to the user.  Is $setcheck set to a list containing x?
	(cond ((or (null $setcheck)
		   (eq $setcheck '$setcheck))) ;setcheck "not set"
	      ((and (or (atom $setcheck)
			(memalike x (cdr $setcheck)) ;; setcheck set to x?
			(and (not (atom x)) ;; setcheck set a list with x?
			     (memalike (caar x) (cdr $setcheck))))
		    (not (eq x y)))
	       ;; the conditions for printing out the trace on set are fulfilled.
	       (displa (list '(mtext) (disp2 x) '| set to | y))
	       ;; now, check to see if we are supposed to wait in a break at this point
	       (if $setcheckbreak
		   (let (($setval y))
		     (merrbreak t)
		     (setq y $setval)))))
    
	(cond ((atom x) ;; typical case is setting an atom to a value
	       (when (or (not (symbolp x)) 
			 (member x '(t nil) :test #'eq) ;can't set t or nil, boolean constants
			 (mget x '$numer)  ;can't set a numeric constant like $%pi or $%e
			 (char= (getcharn x 1) #\&)) ;can't set a string (begins with &)
		 (if munbindp (return nil)) ;dunno what this does. see mlisp.lisp file.
		 ;; an unsettable atom. Signal an error.
		 (if (mget x '$numer)
		      (merror "~:M is a constant. Attempt to reassign it." x)
		  (merror "~:M is an improper left-hand side for an assignment" x))
		 )
	       ;; a settable atom. Let's get on with it.
	       (let ((f (get x 'assign)))
		 (if (and f (or (not (eq x y))
				(member f '(neverset read-only-assign) :test #'eq)))
		     (if (eq (funcall f x y) 'munbindp) (return nil))))
	       (cond ((and (not (boundp x))
			   (not dsksetp)) ;? something about disk?
		      (add2lnc x $values)) ;not previously bound, make a note.
		     ((and (not (eq x y))  ; something about macsyma options?
			   (optionp x))
		      (if $optionset (mtell "~:M option is being set.~%" x))
		      (if (not (eq x '$linenum)) (add2lnc x $myoptions))))
	       
	       (return (set x y))) ;; actually put a value in lisp value cell!
	      
	      ;; ADDITION  8/17/05 RJF thnx to suggestions by S. Macrakis, R. Dodier,;;;;;;;;;;;;
	      ;;check to see if the operator has an mset_extension_operator.
	      ;; If so, this says how to do assignments. Examples, a@b:x. Put mset_extension_operator
	      ;; of $mrecordassign on the atom $@.  To allow [a,b]:[3,4] put op on mlist.
	      ;; arguably we could use mget, mfuncall, and $mset_extension_operator  and
	      ;; allow this to be done at the maxima level instead of lisp.

          ;; X is could be something like (($FOO ARRAY) 42), in which case it is meaningful
          ;; to look for an assignment operator associated either with $FOO itself or with
          ;; $FOO's object type, with "object type" = (CAAR (SYMBOL-VALUE '$FOO)).

          ((let*
            ((x-value (if (boundp (caar x)) (symbol-value (caar x))))
             (mset-extension-op
                (cond
                  ((get (caar x) 'mset_extension_operator))
                  ((and (not (atom x-value)) (get (caar x-value) 'mset_extension_operator))))))
            (if mset-extension-op
              (return-from mset (funcall mset-extension-op x y)))))

	      ;; x is not an atom, but something like (($M array simp) 12)
	      ((member 'array (cdar x) :test #'eq)
	       (return (arrstore x y))) ;; do the array store
	      
	  ;;    ((and $subscrmap (member (caar x) '(mlist $matrix) :test #'eq));; deprecated.
	 ;;      (return (outermap1 'mset x y)))

	      (t (merror "Improper left-hand side for an assignment:~%~M" x)))))

;;; starting here..
(setf (get '$@ 'mset_extension_operator) '$mrecordassign)

;;; new programs by Richard Fateman 8/14/05
;;  defstruct(f(x,y,z));
;;  myrecord: new(f);
;;  myrecord@y:45;
;;  myrecord;  ==>   f(x,45,z)


;; initializers are possible
;; defstruct(f(x,y=3.14159, z));
;; ff:new(f)  ==>   f(x,3.14159,z)
;; ff@y:2.71828 ==>  ff is  f(x,2.71828,z).

;; the @ syntax can also be used instead of substinpart.

;; k:  h(g(aa,bb),cc);
;; k@1@2:dd; change aa to dd.
;; k;

;;; This definition and the ones following are needed to get the @ stuff going
;;(defparameter mset_extension_operators    '(($@ . $mrecordassign)))

	
(defmfun $mrecordassign (atted value)
  ;; assume atted is  (($@..)  instance-name  field-name)
  ;; should insert some checking code here
  (let* ((in (cadr atted))		;instance
	 (fn (caddr atted))		;field
	 (obj (meval in))
	 (index (if (integerp fn) fn ;;; allow foo@3, also
	      (position fn ($get (caar obj) '$defstruct_template))))
	 ) ;field->integer
    
    (if (null index) (merror "Unknown field in record:~%~M" fn))
    (if  (< 0 index (length obj)) (setf (elt obj index) value)
      (merror "Illegal instance:~%~M @ ~M" in fn))
    value))


(defmfun $@ (in fn)
  (if (not (listp in))(list '(%@) in fn) ;noun form
    (let* ((index  
	    (if (integerp fn) fn ;;; allow foo@3, also
	      (position fn ($get (caar in) '$defstruct_template))))) ;field->integer
    (if (null index) (merror "Unknown field in record:~%~M" fn))
    (if  (< 0 index (length in))
	(elt in index) (merror "Illegal instance:~%~M @ ~M" in fn))
   )))


;; This will not work for compiled code.

;; L looks like defstruct (foo(...), bar(...), baz(...)).
;; Process each argument and return a list of declared structures.

(defmspec $defstruct (L)
  `((mlist) ,@(mapcar 'defstruct1 (cdr L))))

(defun defstruct1 (z) ;; z will look like (($whatever) $a $b $c)
   ;; store the template on $whatever
  ($put (caar z) (namesonly z) '$defstruct_template)
  ;; set the initialization on $whatever
  ($put (caar z) (initializersmostly z) '$defstruct_default)
  z)

(defun namesonly(r)			; f(a,b,c) unchanged, f(a=3,b=4,c=5) -> f(a,b,c)
  (cons (car r)(mapcar #'(lambda(z)
			   (cond((symbolp z) z)
				((eq (caar z) 'mequal)(second z))
				(t (merror "~% Expected record initializer, not ~M." z))))
		       (cdr r))))
(defun initializersmostly(r);; f(a=3,b,c=5) -> f(3,b,5)
  (cons (car r)(mapcar #'(lambda(z)
			   (cond((symbolp z) z)
				((eq (caar z) 'mequal)(third z))
				(t (merror "~% Expected record initializer, not ~M." z))))
		       (cdr r))))

(defmspec $new (h)
  (if (not (= (length (cdr h)) 1))
    (merror "~% new: expected exactly one argument, not ~M." (length (cdr h))))

  (let ((recordname (cadr h)))
    (cond
      ((symbolp recordname)  ;; the case of, e.g.  new(f);
       (if (null ($get recordname '$defstruct_default))
         (merror "~% new: don't know anything about `~M'." recordname))

       (copy-tree ($get recordname '$defstruct_default)))

      ;; assume there is some initialization here e.g. new (f(5,6,7))
      (t
        (let ((recordop (caar recordname)) (recordargs (cdr recordname)))
          (if (null ($get recordop '$defstruct_default))
            (merror "~% new: don't know anything about `~M'." recordop))

          (if (not (= (length recordargs) (length (cdr ($get recordop '$defstruct_default)))))
            (merror "~% new: wrong number of arguments in initializer; expected ~M, not ~M."
                    (length (cdr ($get recordop '$defstruct_default))) (length recordargs)))

          (copy-tree recordname))))))

;; this is the lisp code equivalent to executing the command
;; infix(@);
;; 

(defprop $@ %@ verb) 
(defprop $@ &@ op) 
(defprop &@ $@ opr) 
;;(add2lnc '&@ $props) 
(define-symbol '&@) 
(defprop $@ dimension-infix dimension) 
(defprop $@ (#\space #\@ #\space) dissym) 
(defprop $@ msize-infix grind) 
(defprop $@ 180 lbp) 
(defprop $@ 180 rbp) 
(defprop $@ parse-infix led) 
(defprop %@ dimension-infix dimension) 
(defprop %@ (#\space #\@ #\space) dissym) 
(defprop %@ $@ noun) 

;;;;;;;;;;;;;8/15/05 RJF
;;  after reading in the redefinition of mset and mset_extension_operators
;; (not necessarily the @ stuff)
;; the follow code implements PARALLEL LIST assignment.
;; it is consistent with commercial macsyma.  [a,b,c]:[x,y,z] means
;;  about the same as a:x, b:y, c:z.  Actually it
;; evaluates x,y,z  BEFORE any assignments to a,b,c, hence parallel.
;; Also implemented is [a,b,c]:x  which evaluates x once and assigns
;; to a,b,c.
;; value returned is (evaluated x to ex)  [ex,ex,ex].


;; quiz .  [a,b]:[b,2*a].  produces values a=b, b= 2*a.
;; re-execute the statement 4 times. what do you get?  [4b, 8a]
;;         
;; a neat application of parallel assignment is this version of
;; a gcd algorithm (for integers)...
;; kgcd(a,b):=(while b#0 do [a,b]:[b,remainder(a,b)], abs(a));
;; The extended euclidean algorithm looks even better with parallel
;; assignment.

;; add MLIST to possible operators on the left hand side of 
;; an assignment statement.

(setf (get 'mlist 'mset_extension_operator) '$mlistassign)

(defmfun $mlistassign (tlist vlist)
  ;;  tlist is  ((mlist..)  var[0]... var[n])  of targets
  ;; vlist is either((mlist..)  val[0]... val[n]) of values
  ;; or possibly just one value.
  ;; should insert some checking code here
  (if (and (listp vlist)
	   (eq (caar vlist) 'mlist)
	   (not (= (length tlist)(length vlist))))
      (merror "Illegal list assignment: different lengths of ~M and ~M." tlist vlist))
  (unless (and (listp vlist)
	   (eq (caar vlist) 'mlist))
    (setf vlist (cons (car tlist) ;; if [a,b,c]:v  then make a list [v,v,v]
		      (make-sequence 'list (1-(length tlist)) :initial-element vlist))))
  (map nil #'mset (cdr tlist)(cdr vlist))
   vlist)
