;;;;
;;;; Additional Common Lisp Functions for XLISP-STAT 2.1
;;;; XLISP-STAT 2.1 Copyright (c) 1990, by Luke Tierney
;;;; Additions to Xlisp 2.1, Copyright (c) 1989 by David Michael Betz
;;;; You may give out copies of this software; for conditions see the file
;;;; COPYING included with this distribution.
;;;;

(setf (get 'first '*setf*)  #'(lambda (x y) (setf (nth 0 x) y)))
(setf (get 'second '*setf*) #'(lambda (x y) (setf (nth 1 x) y)))
(setf (get 'third '*setf*)  #'(lambda (x y) (setf (nth 2 x) y)))
(setf (get 'fourth '*setf*) #'(lambda (x y) (setf (nth 3 x) y)))

;;;;
;;;;
;;;; Defsetf and documentation functions
;;;;
;;;;

(defun apply-arg-rotate (f args) 
  (apply f (car (last args)) (butlast args)))

; (defsetf) - define setf method
(defmacro defsetf (sym first &rest rest)
"Syntax: (defsetf sym fcn [doc])
Installs #'FCN as setf method for SYM."
  (if (symbolp first)
      `(progn (setf (get ',sym '*setf*) #',first) ',sym)
      (let ((f `#'(lambda ,(append (car rest) first) ,@(cdr rest)))
            (args (gensym)))
        `(progn
          (setf (get ',sym '*setf*) 
                #'(lambda (&rest ,args) (apply-arg-rotate ,f ,args)))
          ',sym))))

;; (load-help) - read in file positions fo accessing help info.
(defun load-help ()
  (when 
   (and (null *help-loaded*) (streamp *help-stream*))
   (princ "loading in help file information - this will take a minute ...")
   (force-output)
   (setq *help-loaded* t)
   (file-position *help-stream* 0)
   (do ((item (read *help-stream* nil '*eof*) 
              (read *help-stream* nil '*eof*))) 
       ((eq item '*eof*))
       (cond
         ((and item
               (symbolp item) 
               (null (get item 'function-documentation)))
          (setf (get item 'function-documentation) 
                (file-position *help-stream*)))
         ((consp item)
         	(case (cadr item)
         	  (function (if (null (get (car item) 'function-documentation))
         		            (setf (get (car item) 'function-documentation) 
                                  (file-position *help-stream*))))
              (variable (if (null (get (car item) 'variable-documentation))
         		            (setf (get (car item) 'variable-documentation) 
                                  (file-position *help-stream*))))
              (type (if (null (get (car item) 'type-documentation))
         		        (setf (get (car item) 'type-documentation) 
                              (file-position *help-stream*))))
              (object
               (if (and (boundp (car item))
                        (objectp (symbol-value (car item)))
                        (null (send (symbol-value (car item))
                                    :internal-doc
                                    (caddr item))))
                   (send (symbol-value (car item))
                        :documentation
                        (caddr item)
                        (file-position *help-stream*))))))))
   (princ "done")
   (terpri)))

(defun documentation (symbol doc-type)
"Args: (symbol doc-type)
Returns SYMBOL documentation of type DOC-TYPE."
  (load-help)
  (let ((doc  (case doc-type
                (function (get symbol 'function-documentation))
                (variable (get symbol 'variable-documentation))
                (type     (get symbol 'type-documentation))
                (setf     (get symbol 'setf-documentation)))))
    (when (and (numberp doc) (streamp *help-stream*))
          (file-position *help-stream* doc)
          (setq doc (read *help-stream*)))
    doc))

(defsetf documentation (symbol doc-type) (value)
  (case doc-type
    (function (setf (get symbol 'function-documentation) value))
    (variable (setf (get symbol 'variable-documentation) value))
    (type     (setf (get symbol 'type-documentation) value))
    (setf     (setf (get symbol 'setf-documentation) value))))
  
;;;;
;;;;
;;;; Modules, provide and require
;;;;
;;;;

(defvar *modules*)
    
(defun provide (name)
"Args: (name)
Adds NAME to the list of modules."
  (pushnew name *modules* :test #'equal))
  
(defun require (name &optional (path name))
"Args: (name)
Loads module NAME, unless it has already been loaded. If PATH is supplied it
is used as the file name; otherwise NAME is used. If file NAME is not in the
current directory *default-path* is searched."
  (let ((name (string name))
        (path (string path)))
    (unless (member name *modules* :test #'equal)
            (if (load path)
                t
#+macintosh     (let ((vol (set-volume)))
                  (unwind-protect (load path)
                                  (set-volume vol)))
#-macintosh     (load (strcat *default-path* path))))))

;;;;
;;;;
;;;; Miscellaneous Functions
;;;;
;;;;

; setf method for select function
(defsetf select set-select)

(defun vectorp (x)
"Args: (m)
Returns T if M is a vector, NIL otherwise."
  (and (arrayp x) (= (array-rank x) 1)))

(defun matrixp (x)
"Args: (m)
Returns T if M is a matrix, NIL otherwise."
  (and (arrayp x) (= (array-rank x) 2)))

(defun equalp (x y)
"Args: (x y)
Returns T if (equal x y), or x, y are numbers and (= x y), or
x and y are strings and (string-equal x y)."
  (or (equal x y) 
      (and (numberp x) (numberp y) (= x y))
      (and (stringp x) (stringp y) (string-equal x y))))

(defun y-or-n-p (&rest args)
"Args: (&rest args)
Prints STRING, if provided,  and reads an answer until an answer of Y or N
is obtained. Returns T for Y, NIL for N."
  (do ((answer nil (read))) ((member answer '(y n)) (eq answer 'y))
    (if args (apply #'format t args))
    (princ " (Y/N)")))
    
(defmacro push (a l)
"Syntax: (push item place)
Pushes ITEM onto list in generalized variable PLACE."
  `(if ,l 
       (let ((temp ,l))
         (rplacd temp (cons (car ,l) (cdr ,l)))
         (rplaca temp ,a))
       (setf ,l (cons ,a NIL))))

(defmacro pushnew (a l &rest args)
"Syntax: (push item place &key :test :test-not)
Pushes ITEM onto PLACE if it is not already there."
  `(unless (member ,a ,l ,@args) (push ,a ,l) nil))

(defun getf (place indicator &optional default)
"Args: (place indicator &optional default)
Returns property value of INDICATOR in PLACE, or DEFAULT if not found."
  (let ((mem (member indicator place :test #'eq)))
    (if mem (second mem) default)))

(defun functionp (x)
"Args: (x)
Returns T if X is a legal argument to FUNCALL, NIL otherwise."
    (or (eq (type-of x) 'closure)
        (eq (type-of x) 'subr)
        (symbolp x)
        (and (consp x) (eq (car x) 'lambda))))

(defun count (x seq &key (test #'eql))
"Args (x seq &key (test #'eql))
Counts the number of times X occurs in SEQ, using TEST for matching."
  (reduce #'(lambda (sum y) (if (funcall test x y) (+ sum 1) sum)) 
          seq :initial-value 0))

(defmacro with-input-from-string (stream-string &rest body)
"Syntax: (with-input-from-string (stream string) {form}*)
Opens stream for reading from STRING, binds to STREAM and evaluates
FORMs with this binding."
  (let ((stream (first stream-string))
        (string (second stream-string)))
    `(let ((,stream (make-string-input-stream ,string)))
       (progn ,@body))))

(defmacro with-output-to-string (str-list &rest body)
"Syntax: (with-output-to-string (stream) {form}*)
Opens string output stream, binds to STREAM and evaluates FORMs with
this binding. Returns output stream string."
  (let ((stream (first str-list)))
    `(let ((,stream (make-string-output-stream)))
       (progn ,@body)
       (get-output-stream-string ,stream))))

(defmacro with-open-file (stream-file-args &rest body)
"Syntax: (with-open-file (stream filename {options}*) {form}*)
Opens file stream for FILENAME with specified options, binds to 
STREAM and evaluates FORMs with this binding. Closes stream regardless
of errors."
  (let ((stream (first stream-file-args))
	(file-args (rest stream-file-args)))
    `(let ((,stream (open ,@file-args)))
       (unwind-protect 
	   (progn ,@body)
	 (if ,stream (close ,stream))))))

(defun realp (x)
"Args: (x)
Returns true if X is a real number."
  (or (integerp x) (floatp x)))

;;;;
;;;;
;;;; Additional Common Lisp Functions for Xlisp 2.0
;;;; From the init.lsp file supplied in the Xlisp distribution
;;;;
;;;;

; (unintern sym) - remove a symbol from the oblist
(defun unintern (symbol)
"Args: (symbol)
Makes SYMBOL no longer present in *OBARRAY*.  Returns T if SYMBOL was present;
NIL otherwise."
  (let ((subhash (hash symbol (length *obarray*))))
    (cond ((member symbol (aref *obarray* subhash))
             (setf (aref *obarray* subhash)
                   (delete symbol (aref *obarray* subhash)))
             t)
          (t nil))))

; (mapcan fun list [ list ]...)
(defmacro mapcan (&rest args) `(apply #'nconc (mapcar ,@args)))

; (mapcon fun list [ list ]...)
(defmacro mapcon (&rest args) `(apply #'nconc (maplist ,@args)))

; (set-macro-character ch fun [ tflag ])
(defun set-macro-character (ch fun &optional tflag)
    (setf (aref *readtable* (char-int ch))
          (cons (if tflag :tmacro :nmacro) fun))
    t)

; (get-macro-character ch)
(defun get-macro-character (ch)
  (if (consp (aref *readtable* (char-int ch)))
    (cdr (aref *readtable* (char-int ch)))
    nil))

;;;;
;;;;
;;;; Additional System Functions for Xlisp 2.0
;;;; From the init.lsp file supplied in the Xlisp distribution
;;;;
;;;;

; (savefun fun) - save a function definition to a file
(defmacro savefun (fun)
"Args: (fun)
Safe function definition of symbol FUN to file FUN.lsp."
  `(let* ((fname (strcat (symbol-name ',fun) ".lsp"))
          (fval (get-lambda-expression (symbol-function ',fun)))
          (fp (open fname :direction :output)))
     (cond (fp (print (cons (if (eq (car fval) 'lambda)
                                'defun
                                'defmacro)
                            (cons ',fun (cdr fval))) fp)
               (close fp)
               fname)
           (t nil))))

; (debug) - enable debug breaks
(defun debug () 
"Args: ()
Enable breaking on error on."
  (setq *breakenable* t))

; (nodebug) - disable debug breaks
(defun nodebug ()
"Args: ()
Disable breaking on error on."
  (setq *breakenable* nil))

; (untrace) - patched to allow zero arguments
#+xlisp (setf (symbol-function '|untrace|) (symbol-function 'untrace))
#+xlisp (defmacro untrace (&rest args) `(|untrace| ,@(if args args (trace))))
