;;; This is a Spice -*-Lisp-*- file.

;;; **********************************************************************
;;; This code was written as part of the Spice Lisp project at
;;; Carnegie-Mellon University, and has been placed in the public domain.
;;; Spice Lisp is currently incomplete and under active development.
;;; If you want to use this code or any part of Spice Lisp, please contact
;;; Scott Fahlman (FAHLMAN@CMUC). 
;;; **********************************************************************


;;;
;;; Spice Lisp printer.
;;; Written by Neal Feinberg, Spice Lisp Group.
;;; Currently maintained by Skef Wholey.
;;; 
;;; *******************************************************************
;;; You cannot compile this file properly without having the defstruct for
;;; STREAM compiled first.  Try compiling DEFSTREAM before compiling this
;;; file.

(defvar *print-escape* T
  "Flag which indicates that slashification is on.  See the manual")
(defvar *print-pretty* ()
  "Flag which indicates that pretty printing is to be used")
(defvar *print-base* 10.
  "The output base for integers and rationals.")
(defvar *print-radix* ()
  "This flag requests to verify base when printing rationals.")
(defvar *print-level* ()
  "How many levels deep to print.  Unlimited if null.")
(defvar *print-length* ()
  "How many elements to print on each level.  Unlimited if null.")
(defvar *print-circle* ()
  "Whether to worry about circular list structures. See the manual.")
(defvar *print-case* ':upcase
  "What kind of case the printer should use by default")
(defvar previous-case ()
  "What the previous case selection the printer was set to.")
(defvar *print-array* ()
  "Whether the array should print it's guts out")

(defvar funny-symbol-char-table ()
  "Table of boolean values indexed by charcode.  The table is loaded
   at run time from funny-symbol-char-list by the PRINT-INIT function (q.v.)")

(defvar funny-symbol-char-list
  '(#\( #\) #\# #\: #\, #\; #\' #\` #\\ #\" #\space #\tab #\return #\linefeed)
  "List of characters which when found in a symbol's name must be
   slashed if the symbol is printed by PRIN1 or PRINT.")

;; This variable contains the current definition of one of three symbol
;; printers.  This variable is set by the various print function (PRINT, 
;; PRINC, and PRIN1).

(defvar internal-symbol-output-function ())

;; Initialized by Print Init.  
(defvar print-string-stream ()
  "Holds the string stream for the x-TO-STRING functions.")

;; Part of the floating point printing code
(defvar *digit-string* () "Array of digits generated by FLONUM-TO-STRING")

;; Imported from reader
(declare (special *read-default-float-format*))

;; From the package system
(declare (special *package* *keyword-package*))

;; This macro returns code which maps over a string, binding VARIABLE to each
;; successive character in the string INIT-FORM, and executing BODY with
;; the variable so bound.  This function used to be part of Common Lisp, but
;; is no more.  It lives on in the printer, though.

(defmacro dostring (varform &rest body)
  ;; Varform looks like (variable init-form terminate-form)
  (let ((variable (car varform))
	(init-form (cadr varform))
	(terminate-form (caddr varform)))
    `(do ((,variable)
	  (index 0 (1+ index))
	  (terminate-index (length (the string ,init-form))))
	 ((= index terminate-index)
	  ,terminate-form)
       (setq ,variable (char ,init-form index))
       ,@body)))


;;; Print-Init. The printer initialization funtion.
;;; This function loads up the print table FUNNY-SYMBOL-CHAR-TABLE
;;; with either T or NIL, depending on whether or not the character in
;;; question requires a slash if it is printed by PRIN1 (or PRINT) and
;;; occurs in a symbol.  Any other printer initialization should go here.
;;; This function is called at system startup time.  Because the cold
;;; load can't do arbitrary Lispy things, this function is used to
;;; cons up the funny symbol char table and to cons up a printer
;;; string stream.

(defun print-init ()
  (setq *digit-string*
    (make-array 50 :element-type 'string-char :fill-pointer 0 :adjustable t))
  (setq funny-symbol-char-table
	(make-vector char-code-limit :initial-element nil)
	print-string-stream
	(make-string-output-stream))
  ;; Load up the table with the special syntax characters in FUNNY-SYMBOL-CHAR-LIST.
  (mapcar #'(lambda (char)
	      (setf (svref funny-symbol-char-table (char-code char)) T))
	  funny-symbol-char-list)
  ;; Also, any lowercase characters in symbols are consider funny.
  (do ((index (char-code #\a) (1+ index)))
      ((> index (char-code #\z)))
    (setf (svref funny-symbol-char-table index) T)))



(Declare (inline setup-printer-state))

;; This function sets the internal global symbol 
;; "internal-symbol-output-function" to the right function depending on 
;; the value of *print-case*.  See the manual for details.  The print buffer
;; stream is also reset.

(defun setup-printer-state ()
  (unless (eq *standard-output* print-string-stream)
    (unless (eq *print-case* previous-case)
      (setq previous-case *print-case*)
      (setq internal-symbol-output-function
	    (case *print-case*
	      (:upcase #'output-uppercase-symbol)
	      (:downcase #'output-lowercase-symbol)
	      (:capitalize #'output-capitalize-symbol)
	      (T (let ((bad-case *print-case*))
		   (setq *print-case* :upcase)
		   (Error "Invalid *print-case* value: ~s" bad-case))))))))

;;; Toplevel print functions

(defun write (object &rest keywords)
  "Outputs OBJECT to the specified stream, defaulting to *standard-output*"
  (setup-printer-state)
  (with-keywords keywords
     ((:stream  *standard-output*   *standard-output*)
      (:escape  *print-escape*      *print-escape*)
      (:radix   *print-radix*       *print-radix*)
      (:base    *print-base*        *print-base*)
      (:circle  *print-circle*      *print-circle*)
      (:pretty  *print-pretty*      *print-pretty*)
      (:level   *print-level*       *print-level*)
      (:length  *print-length*      *print-length*)
      (:case    *print-case*        *print-case*)
      (:array   *print-array*       *print-array*))
   (output-object object)))

(defun prin1 (object &optional (*standard-output* *standard-output*))
  "Outputs a mostly READable printed representation of OBJECT on the specified
   stream."
  (setup-printer-state)
  (let ((*print-escape* T))
    (if *print-pretty*
	(output-pretty-object object)
	(output-object object)))
  object)

(defun princ (object &optional (*standard-output* *standard-output*))
  "Outputs an asthetic but not READable printed representation of OBJECT on the
   specified stream."
  (setup-printer-state)
  (let ((*print-escape* NIL))
    (if *print-pretty*
	(output-pretty-object object)
	(output-object object)))
  object)

(defun print (object &optional (*standard-output* *standard-output*))
  "Outputs a terpri, the mostly READable printed represenation of OBJECT, and 
   space to the stream."
  (terpri)
  (prin1 object)
  (write-char #\space *standard-output*)
  object)


;; STRINGIFY-OBJECT is an internal printer function produces the printed
;; representation of an object as a string.  It is called by various x-TO-STRING
;; functions below.

(defun stringify-object (object &optional (*print-escape* ()))
  (let ((*standard-output* print-string-stream))
    (setup-printer-state)
    (output-object object 0)
    (get-output-stream-string *standard-output*)))

;;; Top-level x-TO=STRING functions.  These functions all take an object
;;; and return that object's printed representation as a string. 

(defun write-to-string (object &rest keywords)
  "Returns the printed representation of OBJECT as a string."
  (with-keywords keywords
     ((:stream  *standard-output*   *standard-output*)
      (:escape  *print-escape*      *print-escape*)
      (:radix   *print-radix*       *print-radix*)
      (:base    *print-base*        *print-base*)
      (:circle  *print-circle*      *print-circle*)
      (:pretty  *print-pretty*      *print-pretty*)
      (:level   *print-level*       *print-level*)
      (:length  *print-length*      *print-length*)
      (:case    *print-case*        *print-case*)
      (:array   *print-array*       *print-array*))
   (stringify-object object *print-escape*)))

(defun prin1-to-string (object)
  "Returns the printed representation of OBJECT as a string with 
   slashification on."
  (stringify-object object t))

(defun princ-to-string (object)
  "Returns the printed representation of OBJECT as a string with
  slashification off."
  (stringify-object object nil))


;;; Central print functions.  

;;; OUTPUT-OBJECT takes an object and outputs its printed representation to
;;; *STANDARD-OUTPUT*, which is typically bound the the internal print stream.
;;; This function is called recursively by the sub-functions which know how
;;; to print structures which can contain other lisp objects.

(defun output-object (object &optional (currlevel 0))
  "Outputs a string which is the printed representation of the given object."
  ;; First check and make sure we aren't too deep
  (cond ((and (not (null *print-level*))
	      (>= currlevel *print-level*))
	 (write-char #\#))
	(T (typecase object
	     (symbol
	      (let ((package (symbol-package object)))
		(cond
		 ;; If the symbol is in the keyword package, output a colon.
		 ((eq package *keyword-package*)
		  (write-char #\:))
		 ;; If the symbol is in the current package, output nothing.
		 ((eq package *package*))
		 ;; Uninterned symbols print with a leading #:.
		 ((null package)
		  (write-string "#:"))
		 (t
		  (let* ((uses (package-use-list *package*))
			 (it (car (memq package uses))))
		    (cond
		     ;; If the symbol is in a package used by *package*, but
		     ;; it is not external, qualify it.
		     (it
		      (when (eql (gethash (symbol-name object)
					  (package-external-symbols it)
					  0)
				 0)
			(write-string (package-name package))
			(write-string "::")))
		     ;; If the symbol is external, :, else ::.
		     (t
		      (write-string (package-name package))
		      (if (gethash (symbol-name object)
				   (package-external-symbols package))
			  (write-char #\:)
			  (write-string "::"))))))))
	      (funcall internal-symbol-output-function object))
	     ;; If a list, go through element by element, being careful
	     ;; about not running over the printlength
	     (list
	      (output-list object (1+ currlevel)))
	     (string
	      (if *print-escape*
		  (quote-string object)
		  (write-string object)))
	     (integer
	      (output-integer object))
	     (float
	      (output-float object))
	     (ratio
	      (output-ratio object))
	     (structure
	      (output-structure object currlevel))
	     (character
	      (output-character object))
	     (vector
	      (output-vector object))
	     (array
	      (output-array object (1+ currlevel)))
	     (t (output-random object)))))
  'T)


;;; Symbol Printing Subfunctions

(declare (inline funny-symbol-char-p))

(defun funny-symbol-char-p (char)
  "Determines whether or not the character should be proceded by a slash when
   outputing the printed representation of a symbol containing it."
   ;; Should really look at readtable, not quite ready for such
   ;; a feat so use optimized table lookup hack
  (svref funny-symbol-char-table (char-code char)))


;; This function is hooked into the variable internal-symbol-output-function 
;; when the value of *print-case* is :upcase.  If *print-escape* is NIL, just 
;; output the symbol as is (just the way the reader set the print-name).
;; Otherwise just add slashes before the lowercase characters and any other 
;; strange characters.

(defun output-uppercase-symbol (symbol)
  (if *print-escape*
      (quote-symbol-uppercase (symbol-name symbol))
      (write-string (symbol-name symbol))))

;; This function is hooked into the variable internal-symbol-output-function
;; when the value of *print-case* is :downcase.  If *print-escape* is NIL, 
;; lowercase all the characters and output the symbol.  If it is T, make sure
;; every character is lowercase, and slash those letters that already are.

(defun output-lowercase-symbol (symbol)
  (if *print-escape*
      (quote-symbol-lowercase (symbol-name symbol))
      (write-string (string-downcase (symbol-name symbol)))))

;; This function is hooked into the variable internal-symbol-output-function
;; when the value of *print-case* is :capitalize.  If *print-escape* is NIL, 
;; lowercase all the characters (except the first, leave that alone) and 
;; output the symbol.  If it is T, make sure every character is 
;; lowercase (except the first), and slash those letters that already are.


(defun output-capitalize-symbol (symbol)
  (if *print-escape*
      (quote-symbol-capitalize (symbol-name symbol))
      (if (upper-case-p (char (symbol-name symbol) 0))
	  (write-string (string-capitalize (symbol-name symbol)))
	  (write-string (string-downcase (symbol-name symbol))))))


(defmacro symbol-quote-char-p (char)
  "Determines whether or not the character is a symbol quoter."
  ;; Should really look at readtable, not ready yet for that.
  `(or (char= ,char #\\)
       (char= ,char #\|)))

;;;
;;; This function takes the pname of a symbol and adds slashes and/or 
;;; vertical bars to it to make it readable again.
;;; Special quoting characters are currently vertical bar and slash who's 
;;; role in life are to specially quote symbols.  Funny symbol characters
;;; are those who need special slashification when they are to be printed
;;; so they can be read in again.  These currently include such characters 
;;; as hash signs, colons of various sorts, etc.
;;; Now there are three different version: UPPERCASE, lowercase and Captialize.
;;; Check out the manual under the entry for *print-case* for details.

(defun quote-symbol-uppercase (pname)
  (declare (simple-string pname))
  (let ((vertical-bar-flag
	 (or
	  (not (null (find-if #'funny-symbol-char-p pname)))
	  (do ((index 0 (1+ index))
	       (length (length pname)))
	      ((= index length) t)
	    (if (not (digit-char-p (char pname index) *print-base*))
		(return ()))))))
    ;;If we will be using vertical bars to quote instead slashes
    (if vertical-bar-flag (write-char #\|))
    (dostring (char pname)
      ;;If it needs slashing, do it.
      (if (symbol-quote-char-p char)
	  (write-char #\\))
      (write-char char))
    (if vertical-bar-flag (write-char #\|))))

;; See documentation for quote-symbol-uppercase (above).

(defun quote-symbol-lowercase (pname)
  (declare (simple-string pname))
  (let ((vertical-bar-flag
	 (or
	  (not (null (find-if #'funny-symbol-char-p pname)))
	  (do ((index 0 (1+ index))
	       (length (length pname)))
	      ((= index length) t)
	    (if (not (digit-char-p (char pname index) *print-base*))
		(return ()))))))
    ;;If we will be using vertical bars to quote instead slashes
    (if vertical-bar-flag (write-char #\|))
    (dostring (char pname)
      ;;If it needs slashing, do it.
      (if (symbol-quote-char-p char)
	  (write-char #\\))
      (write-char (char-downcase char)))
    (if vertical-bar-flag (write-char #\|))))

;; See documentation for quote-symbol-uppercase (above).

(defun quote-symbol-capitalize (pname)
  (declare (simple-string pname))
  (let ((vertical-bar-flag
	 (or
	  (not (null (find-if #'funny-symbol-char-p pname)))
	  (do ((index 0 (1+ index))
	       (length (length pname)))
	      ((= index length) t)
	    (if (not (digit-char-p (char pname index) *print-base*))
		(return ()))))))
    ;;If we will be using vertical bars to quote instead slashes
    (if vertical-bar-flag (write-char #\|))
    (do ((index 0 (1+ index))
	 (pname-length (length (the string pname))))
	((= index pname-length))
      (let ((char (char pname index)))
	;;If it needs slashing, do it.
	(if (symbol-quote-char-p char)
	    (write-char #\\))
	(write-char (if (= index 0) char (char-downcase char)))))
    (if vertical-bar-flag (write-char #\|))))



;;; Recurisve Datatype Printing Subfunctions


(defun output-list (list &optional (currlevel 0))
  "Outputs the printed representation of a list."
  (write-char #\()
  (do ((list list (cdr list))
       (currlength 0 (1+ currlength)))
      ;; Terminate when we've gotten to the end of the list, or we've printed
      ;; too much (according to *print-length*).
      ((or (null list)
	   (and (not (null *print-length*))
		(>= currlength *print-length*)))
       ;; If we've terminated but had more to print
       ;; we notify with ellipses
       (if (not (null list)) (write-string "..."))
       (write-char #\)))
    ;;If we are not printing the first object, we should space first.
    (if (> currlength 0) (write-char #\space))
    ;;Print whatever the car of the list is, at this level.
    (output-object (car list) currlevel)
    ;; Check for the non-null terminated list.  If present, print
    ;; " . <non-null-terminator>)" and terminate printing of list.
    (cond ((not (or (consp (cdr list))
		    (null (cdr list))))
	   (write-string " . ")
	   (output-object (cdr list) currlevel)
	   (write-char #\))
	   (return ())))))

(defun output-vector (vector &optional (currlevel 0))
  "Outputs the printed representation of a 1-D array."
  (cond ((not *print-array*)
	 (output-terse-array vector currlevel))
	(T
	 (if (bit-vector-p vector)
	     (write-string "#*")
	     (write-string "#("))
	 (do ((currlength 0 (1+ currlength))
	      (vlength (length (the vector vector)))
	      (not-bit-vector-p (not (bit-vector-p vector))))
	     ;;Terminate at end, or when too much has been output
	     ((or (and (not (null *print-length*))
		       (>= currlength *print-length*))
		  (= currlength vlength))
	      (if (not (= currlength vlength)) (write-string "..."))
	      (if not-bit-vector-p
		  (write-char #\))))
	 ;;Put a space before every element except the first
	 ;; and not in bit vectors.
	 (if (and (> currlength 0)
		  not-bit-vector-p)
	     (write-char #\space))
	 ;;Output an element of the vector
	 (output-object (aref vector currlength) currlevel)))))

(defun output-array (array &optional (currlevel 0))
  "Outputs the printed representation of any array in either the #< or #A form."
  (let ((rank (array-rank array)))
    (cond ((not *print-array*)
	   (output-terse-array array rank))
	  (T
	   (output-array-guts array rank currlevel)))))

;; Master function for outputing the #A form of an array

(defun output-array-guts (array rank currlevel)
  (write-char #\#)
  (output-integer rank)
  (write-char #\A)
  (sub-output-array-guts (%primitive header-ref array %array-data-slot)
			 (array-dimensions array)
			 currlevel 0))

;; Some Ideas stolen from Skef Wholey.
;; Helping function for above.
(defun sub-output-array-guts (array dimensions currlevel index)
  (cond ((null dimensions)
	 (output-object (%sp-svref array index) currlevel)
	 (1+ index))
	((and (not (null *print-level*))
	      (>= currlevel *print-level*))
	 (write-char #\#) index)
	(T
	 (write-char #\()
	 (do ((index index)
	      (times 0 (1+ times))
	      (limit (pop dimensions)))
	     ((or (= times limit)
		  (and (not (null *print-length*))
		       (= times *print-length*)))
	      (if (not (= times limit))
		  (write-string "...)")
		  (write-char #\)))
	      index)
	   (if (not (zerop times)) (write-char #\space))
	   (setq index
		 (sub-output-array-guts array dimensions (1+ currlevel) index))
	   ))))

;; Used to output the #< form of any array.

(defun output-terse-array (array rank)
  (write-string "#<")
  (cond ((vectorp array)
	 (if (bit-vector-p array)
	     (write-string "Bit-vector")
	     (write-string "Vector")))
	(T
	 (write-string "Array, rank ")
	 (output-integer rank)))
  (finish-random array))


;;; Structure Printing.

;; *** For Spice Lisp Only ***
;;Returns code which fetches the name of a structure.  If it is a vector
;;structure, the name is in slot zero.  Otherwise it must be an 
;;array structure, so look in slot six.
(defmacro access-structure-name (structure)
  `(if (vectorp ,structure)
       (%sp-svref ,structure 0)
       (%sp-svref ,structure 6)))
  
(defun output-structure (structure currlevel)
  (let* ((structure-name (access-structure-name structure))
	 (print-function (get structure-name 'structure-print))
	 (slot-names (defstruct-description-slot-numbers
		       (get structure-name 'defstruct-description))))
    ;;See if it likes printing it's own guts itself.
    ;;If it does, let it.
    (cond ((not (null print-function))
	   (funcall print-function structure *standard-output* currlevel))
	  ((vectorp structure)
	   ;;Otherwise if a print function is not defined.
	   (write-string "#S(")
	   (output-object structure-name currlevel)
	   (do ((index (if (simple-vector-p structure) 1 0)
		       (1+ index))
		(structure-length (length structure)))
	       ((or (= index structure-length)
		    (if (not (null *print-length*))
			(= index *print-length*)
			nil))
		(if (not (= index structure-length))
		    (write-string " ...)")
		    (write-string ")")))
	     (write-char #\space)
	     (output-object (car (rassoc index slot-names)) currlevel)
	     (write-char #\space)
	     (output-object (aref structure index) currlevel))))))


;; Helping functions for printing strings.

(defmacro funny-string-char-p (char)
  "Determines whether or not the character needs quoting in a string."
  ;; Should maybe look at readtable, not quite ready for such feat so
  ;; use hack.
  `(or (char= ,char #\\)
       (char= ,char #\")))

;;;
;;; This function takes a string and slashifies it sufficiently so
;;; that if so that it may be read in again.
;;; Strategy: Put slash before all and only those characters which are
;;; either double-quote or backslash.
;;;

(defun quote-string (string)
  "Quotes a string so that if printed, it could be read back in again."
  (write-char #\")
  (dostring (char string)
    (if (funny-string-char-p char) (write-char #\\))
    (write-char char))
  (write-char #\"))



(defun whitespace-char-p (char)
  "Determines whether or not the character is considered whitespace."
  (or (char= char #\space)
      (char= char #\tab)
      (char= char #\return)
      (char= char #\linefeed)))

;;; Numerical Printer Functions.

;; Helping function for above that actually outputs the individual digits.

(defun output-integer (integer)
  "This function outputs the printed representation of any integer"
  (cond ((not (and (fixnump *print-base*) (> *print-base* 1)))
	 (let ((obase *print-base*))
	   (setq *print-base* 10.)
	   (error "~A is not a reasonable value for *Print-Base*." obase)))
	;; Otherwise print the base
	(T (cond ((and (not (= *print-base* 10.))
		       *print-radix*)
		  ;; First print leading base information, if any.
		  (write-char #\#)
		  (write-char (case *print-base*
			  (2.  #\b)
			  (8.  #\o)
			  (16. #\x)
			  (T (let ((fixbase *print-base*)
				   (*print-base* 10.)
				   (*print-radix* ()))
			       (sub-output-integer fixbase))
			     #\r)))))
	   ;; Then output a minus sign if the number is negative, then output
	   ;; the absolute value of the number.
	   (cond ((bignump integer) (print-bignum integer))
		 ((< integer 0)
		  (write-char #\-)
		  (sub-output-integer (- integer)))
		 (T (sub-output-integer integer)))
	   ;; Print any trailing base information, if any.
	   (if (and (= *print-base* 10.) *print-radix*) (write-char #\.)))))


(defun sub-output-integer (integer)
  (let  ((quotient)
	 (remainder))
    ;; Recurse until you have all the digits pushed on the stack.
    (if (not (zerop (multiple-value-setq (quotient remainder)
		      (truncate integer *print-base*))))
      (sub-output-integer quotient))
    ;; Then as each recursive call unwinds, turn the digit (in remainder) 
    ;; into a character and output the character.
    (write-char (int-char (if (and (> remainder 9.)
			     (> *print-base* 10.))
			(+ (char-int #\A) (- remainder 10.))
			(+ (char-int #\0) remainder))))))


;;; Outputs the printed representation of a ratio.

(defun output-ratio (ratio)
  (output-integer (numerator ratio))
  (write-char #\/)
  (output-integer (denominator ratio)))




;;; Bignum printing
;;;
;;; Written by Steven Handerson
;;;  (based on Skef's idea)
;;;

;;; Uses the bignum-fixnum-divide-inplace function in spnum to do the
;;;  division without consing a new bignum.
;;; We compute as large a possible base power for the in-place division.
;;; We need to copy the bignum anyway, so we either negate (hence copy)
;;;  or copy.  The auxiliary function uses fixnum printing.
;;;

(declare (special *%base-power* *%base-power-1*))

(defun print-bignum (big)
  (let* ((copy (cond ((minusp big) (write-char #\-) (- big))
		     (t (copy-xnum big))))
	 (*%base-power* (do ((newbase (* *print-base* *print-base*)
				      (* newbase *print-base*)))
			    ((> (integer-length newbase) 19)
			     (/ newbase *print-base*))))
	 (*%base-power-1* (/ *%base-power* *print-base*)))
    (bignum-print-aux copy)
    big))

;;; The *%base-power-1* is needed to assure the printing of leading zeros
;;; in the parts of the bignum.

(defun bignum-print-aux (big)
  (multiple-value-bind (newbig fix)
		       (bignum-fixnum-divide-inplace big *%base-power*)
    (if (fixnump newbig) (sub-output-integer newbig)
	(bignum-print-aux newbig))
    (do ((frob (logior *print-base* fix) (* frob *print-base*))) ;For zero.
	((> frob *%base-power-1*) (sub-output-integer fix))
      (write-char #\0))))



;;;; Floating Point printing
;;;
;;;  Written by Bill Maddox
;;;
;;;
;;;
;;; FLONUM-TO-STRING (and its subsidiary function FLOAT-STRING) does most of 
;;; the work for all printing of floating point numbers in the printer and in
;;; FORMAT.  It converts a floating point number to a string in a free or 
;;; fixed format with no exponent.  The interpretation of the arguments is as 
;;; follows:
;;;
;;;     X        - The floating point number to convert, which must not be
;;;                negative.
;;;     WIDTH    - The preferred field width, used to determine the number
;;;                of fraction digits to produce if the FDIGITS parameter
;;;                is unspecified or NIL.  If the non-fraction digits and the
;;;                decimal point alone exceed this width, no fraction digits
;;;                will be produced unless a non-NIL value of FDIGITS has been
;;;                specified.  Field overflow is not considerd an error at this
;;;                level.
;;;     FDIGITS  - The number of fractional digits to produce. Insignificant
;;;                trailing zeroes may be introduced as needed.  May be
;;;                unspecified or NIL, in which case as many digits as possible
;;;                are generated, subject to the constraint that there are no
;;;                trailing zeroes.
;;;     SCALE    - If this parameter is specified or non-NIL, then the number
;;;                printed is (* x (expt 10 scale)).  This scaling is exact,
;;;                and cannot lose precision.
;;;     FMIN     - This parameter, if specified or non-NIL, is the minimum
;;;                number of fraction digits which will be produced, regardless
;;;                of the value of WIDTH or FDIGITS.  This feature is used by
;;;                the ~E format directive to prevent complete loss of
;;;                significance in the printed value due to a bogus choice of
;;;                scale factor.
;;;
;;; Most of the optional arguments are for the benefit for FORMAT and are not
;;; used by the printer.
;;;
;;; Returns:
;;; (VALUES DIGIT-STRING DIGIT-LENGTH LEADING-POINT TRAILING-POINT DECPNT)
;;; where the results have the following interpretation:
;;;
;;;     DIGIT-STRING    - The decimal representation of X, with decimal point.
;;;     DIGIT-LENGTH    - The length of the string DIGIT-STRING.
;;;     LEADING-POINT   - True if the first character of DIGIT-STRING is the
;;;                       decimal point.
;;;     TRAILING-POINT  - True if the last character of DIGIT-STRING is the
;;;                       decimal point.
;;;     POINT-POS       - The position of the digit preceding the decimal
;;;                       point.  Zero indicates point before first digit.
;;;
;;; WARNING: For efficiency, there is a single string object *digit-string*
;;; which is modified destructively and returned as the value of
;;; FLONUM-TO-STRING.  Thus the returned value is not valid across multiple 
;;; calls.
;;;
;;; NOTE:  FLONUM-TO-STRING goes to a lot of trouble to guarantee accuracy.
;;; Specifically, the decimal number printed is the closest possible 
;;; approximation to the true value of the binary number to be printed from 
;;; among all decimal representations  with the same number of digits.  In
;;; free-format output, i.e. with the number of digits unconstrained, it is 
;;; guaranteed that all the information is preserved, so that a properly-
;;; rounding reader can reconstruct the original binary number, bit-for-bit, 
;;; from its printed decimal representation. Furthermore, only as many digits
;;; as necessary to satisfy this condition will be printed.
;;;
;;;
;;; FLOAT-STRING actually generates the digits for positive numbers.  The
;;; algorithm is essentially that of algorithm Dragon4 in "How to Print 
;;; Floating-Point Numbers Accurately" by Steele and White.  The current 
;;; (draft) version of this paper may be found in [CMUC]<steele>tradix.press.
;;; DO NOT EVEN THINK OF ATTEMPTING TO UNDERSTAND THIS CODE WITHOUT READING 
;;; THE PAPER!


(defvar *digits* "0123456789")

;;; The initialization of *digit-string* must be done in PRINT-INIT due
;;; to cold-loader lossage.

;(defvar *digit-string*
;  (make-array 50 :element-type 'string-char :fill-pointer 0 :adjustable t))


(defun flonum-to-string (x &optional width fdigits scale fmin)
  (cond ((zerop x)
	 ;;zero is a special case which float-string cannot handle
	 (values "." 1 t t))
	(t
	  (setf (fill-pointer *digit-string*) 0)
	  (multiple-value-bind (sig exp)
			       (integer-decode-float x)
	    (if (typep x 'short-float)
		;;20 and 53 are the number of bits of information in the
		;;significand, less sign, of a short float and a long float
		;;respectively.
		(float-string sig exp 20 width fdigits scale fmin)
		(float-string sig exp 53 width fdigits scale fmin))))))


(defun float-string (fraction exponent precision width fdigits scale fmin)
  (let ((r fraction) (s 1) (m- 1) (m+ 1) (k 0)
	(digits 0) (decpnt 0) (cutoff nil) (roundup nil) u low high)
    ;;Represent fraction as r/s, error bounds as m+/s and m-/s.
    ;;Rational arithmetic avoids loss of precision in subsequent calculations.
    (cond ((> exponent 0)
	   (setq r (ash fraction exponent))
	   (setq m- (ash 1 exponent))	   
	   (setq m+ m-))                   
	  ((< exponent 0)
	   (setq s (ash 1 (- exponent)))))
    ;;adjust the error bounds m+ and m- for unequal gaps
    (when (= fraction (ash 1 precision))
      (setq m+ (ash m+ 1))
      (setq r (ash r 1))
      (setq s (ash s 1)))
    ;;scale value by requested amount, and update error bounds
    (when scale
      (if (minusp scale)
	  (let ((scale-factor (expt 10 (- scale))))
	    (setq s (* s scale-factor)))
	  (let ((scale-factor (expt 10 scale)))
	    (setq r (* r scale-factor))
	    (setq m+ (* m+ scale-factor))
	    (setq m- (* m- scale-factor)))))
    ;;scale r and s and compute initial k, the base 10 logarithm of r
    (do ()
        ((>= r (ceiling s 10)))
      (decf k)
      (setq r (* r 10))
      (setq m- (* m- 10))
      (setq m+ (* m+ 10)))
    (do ()(nil)
      (do ()
	  ((< (+ (ash r 1) m+) (ash s 1)))
	(setq s (* s 10))
	(incf k))
      ;;determine number of fraction digits to generate
      (cond (fdigits
	     ;;use specified number of fraction digits
	     (setq cutoff (- fdigits))
	     ;;don't allow less than fmin fraction digits
	     (if (and fmin (> cutoff (- fmin))) (setq cutoff (- fmin))))
	    (width
	     ;;use as many fraction digits as width will permit
             ;;but force at least fmin digits even if width will be exceeded
	     (if (< k 0)
		 (setq cutoff (- 1 width))
		 (setq cutoff (1+ (- k width))))
	     (if (and fmin (> cutoff (- fmin))) (setq cutoff (- fmin)))))
      ;;If we decided to cut off digit generation before precision has
      ;;been exhausted, rounding the last digit may cause a carry propagation.
      ;;We can prevent this, preserving left-to-right digit generation, with
      ;;a few magical adjustments to m- and m+.  Of course, correct rounding
      ;;is also preserved.
      (when (or fdigits width)
	(let ((a (- cutoff k))
	      (y s))
	  (if (>= a 0)
	      (dotimes (i a) (setq y (* y 10)))
	      (dotimes (i (- a)) (setq y (ceiling y 10))))
	  (setq m- (max y m-))
	  (setq m+ (max y m+))
	  (when (= m+ y) (setq roundup t))))
      (when (< (+ (ash r 1) m+) (ash s 1)) (return)))

    ;;zero-fill before fraction if no integer part
    (when (< k 0)
      (setq decpnt digits)
      (vector-push-extend #\. *digit-string*)
      (dotimes (i (- k))
	(incf digits) (vector-push-extend #\0 *digit-string*)))
    ;;generate the significant digits
    (do ()(nil)
      (decf k)
      (when (= k -1)
	(vector-push-extend #\. *digit-string*)
	(setq decpnt digits))
      (multiple-value-setq (u r) (truncate (* r 10) s))
      (setq m- (* m- 10))
      (setq m+ (* m+ 10))
      (setq low (< (ash r 1) m-))
      (if roundup
	  (setq high (>= (ash r 1) (- (ash s 1) m+)))
	  (setq high (> (ash r 1) (- (ash s 1) m+))))
      ;;stop when either precision is exhausted or we have printed as many
      ;;fraction digits as permitted
      (when (or low high (and cutoff (<= k cutoff))) (return))
      (vector-push-extend (char *digits* u) *digit-string*)
      (incf digits))
    ;;if cutoff occured before first digit, then no digits generated at all
    (when (or (not cutoff) (>= k cutoff))
      ;;last digit may need rounding
      (vector-push-extend (char *digits*
				(cond ((and low (not high)) u)
				      ((and high (not low)) (1+ u))
				      (t (if (<= (ash r 1) s) u (1+ u)))))
			  *digit-string*)
      (incf digits))
    ;;zero-fill after integer part if no fraction
    (when (>= k 0)
      (dotimes (i k) (incf digits) (vector-push-extend #\0 *digit-string*))
      (vector-push-extend #\. *digit-string*)
      (setq decpnt digits))
    ;;add trailing zeroes to pad fraction if fdigits specified
    (when fdigits
      (dotimes (i (- fdigits (- digits decpnt)))
	(incf digits)
	(vector-push-extend #\0 *digit-string*)))
    ;;all done
    (values *digit-string* (1+ digits) (= decpnt 0) (= decpnt digits) decpnt)))



;;; Given a non-negative floating point number, SCALE-EXPONENT returns a
;;; new floating point number Z in the range (0.1, 1.0] and and exponent
;;; E such that Z * 10↑E is (approximately) equal to the original number.
;;; There may be some loss of precision due the floating point representation.


(defconstant short-log10-of-2 0.30103s0)

;;; Nuke contstant properties on long float constants so Genesis doesn't
;;; choke.

(eval-when (compile)
  (remprop '%long-float-ten '%constant)
  (remprop '%long-float-one-tenth '%constant)
  (remprop 'long-log10-of-2 '%constant))

(defun scale-exponent (x)
  (if (typep x 'short-float)
      (scale-expt-aux x 0.0s0 1.0s0 1.0s1 1.0s-1 short-log10-of-2)
      (scale-expt-aux x (%sp-l-float 0) (%sp-l-float 1) %long-float-ten
		      %long-float-one-tenth long-log10-of-2)))


(defun scale-expt-aux (x zero one ten one-tenth log10-of-2)
  (multiple-value-bind (sig exponent)
		       (decode-float x)
    (if (= x zero)
	(values zero 1)
	(let* ((e (round (* exponent log10-of-2)))
	       (x (if (minusp e)		;For the end ranges.
		      (* x ten (expt ten (- -1 e)))
		      (/ x ten (expt ten (1- e))))))
	  (do ((d ten (* d ten))
	       (y x (/ x d))
	       (e e (1+ e)))
	      ((< y one)
	       (do ((m ten (* m ten))
		    (z y (* z m))
		    (e e (1- e)))
		   ((>= z one-tenth) (values z e)))))))))


;;; Entry point for the float printer as called by PRINT, PRIN1, PRINC,
;;; etc.  The argument is printed free-format, in either exponential or 
;;; non-exponential notation, depending on its magnitude.
;;;
;;; NOTE:  When a number is to be printed in exponential format, it is scaled
;;; in floating point.  Since precision may be lost in this process, the
;;; guaranteed accuracy properties of FLONUM-TO-STRING are lost.  The
;;; difficulty is that FLONUM-TO-STRING performs extensive computations with
;;; integers of similar magnitude to that of the number being printed.  For
;;; large exponents, the bignums really get out of hand.  When we switch to
;;; IEEE format for long floats, this will significantly restrict the magnitude
;;; of the largest allowable float.  This combined with microcoded bignum
;;; arithmetic might make it attractive to handle exponential notation with
;;; the same accuracy as non-exponential notation, using the method described
;;; in the Steele and White paper.


(eval-when (compile)
  (remprop '%long-float1l-3 '%constant)
  (remprop '%long-float1l7 '%constant))

(defun output-float (x)
  (if (typep x 'short-float)
      (output-float-aux x 1.0s-3 1.0s7)
      (output-float-aux x %long-float1l-3 %long-float1l7)))


(defun output-float-aux (x e-min e-max)
  (cond ((zerop x) (write-string "0.0")
		   (if (not (typep x *read-default-float-format*))
		       (write-string (if (typep x 'short-float) "s0" "L0"))))
	(t (when (minusp x) 
	     (write-char #\-)
	     (setq x (- x)))
	   (if (and (>= x e-min) (< x e-max))
	       ;;free format
	       (multiple-value-bind (str len lpoint tpoint)
				    (flonum-to-string x)
		 (when lpoint (write-char #\0))
		 (write-string str)
		 (when tpoint (write-char #\0))
		 (if (not (typep x *read-default-float-format*))
		     (write-string (if (typep x 'short-float) "s0" "L0"))))
	       ;;exponential format 
	       (multiple-value-bind (f e)
				    (scale-exponent x)
		 (multiple-value-bind (str len lpoint tpoint)
				      (flonum-to-string f nil nil 1)
		   (when lpoint (write-char #\0))
		   (write-string str)
		   (when tpoint (write-char #\0))
		   (write-char (if (typep x *read-default-float-format*)
				   #\E
				   (if (typep x 'short-float) #\S #\L)))
		   ;;must subtract 1 from exponent here, due to
		   ;;the scale factor of 1 in call to FLONUM-TO-STRING
		   (unless (minusp (1- e)) (write-char #\+))
		   (output-integer (1- e))))))))


;;; Character Printing Subfunctions.

(defun if-bit (char name)
  (if (char-bit char name)
      (concatenate 'simple-string (the simple-string (symbol-name name)) "-")
      ""))


;; NAME-BITS produces a string naming the bits on in the given character.

(defun name-bits (char)
  (concatenate 'simple-string (if-bit char :CONTROL) (if-bit char :META)
	  (if-bit char :HYPER)   (if-bit char :SUPER)))

;; FUNNY-CHARACTER-CHAR-P returns a predicate which determines whether a
;; character must be slashified when being output.

(defmacro funny-character-char-p (char)
  `(and *print-escape*
	(not (zerop (char-bits ,char)))
	(or (funny-symbol-char-p ,char)
	    (lower-case-p ,char))))


(defun output-character (char)
  "Outputs the printed represenation of the given character."
  (cond ((not *print-escape*)
	 (write-char char))
	(T
	 ;; If we are PRIN1ing output a hash prefix for character
	 ;; with proper slash...
	 (write-string "#\\")
	 ;; Write out the names of any bits this guy has...
	 (write-string (name-bits char))
	 (let* ((char (code-char (char-code char)))
		(name (char-name char)))
	   ;; If he's got a name, print that.
	   (cond (name (write-string name))
		 ;; Otherwise print the character maybe slashifying.
		 (t (if (funny-character-char-p char)
			(write-char #\\))
		    (write-char char)))))))

;;; Random and Misc. Print Subfunctions

;;For Spice Lisp Only! Change these functions for other implementations
;; [access-subr-name, misc-type?, misc-type-code]

;; Returns the name of a subr as a string.  
(defmacro access-subr-name (object)
  `(symbol-name (%primitive header-ref ,object %function-name-slot)))


;; Produces code which returns T iff the argument is of misc type
(defmacro misc-type? (object)
  `(= (%sp-type ,object) %misc-type))


;; Returns the name of the subtype of a misc-typed object.
(defun misc-type-code (object)
  (declare (ignore object))
  "Type 0 object")

;;; OUTPUT-FUNCTION-OBJECT outputs the main part of the printed 
;;; representation of function objects.  It is called from OUTPUT-RANDOM
;;; below.

(defun output-function-object (subr)
  (write-string  "Function ")
  (write-string (access-subr-name subr)))


;;; FINISH-RANDOM is a helping function for OUTPUT-RANDOM below.  
;;; It outputs the numerical value of the low 28 bits of 
;;; RANDOM-OBJECT, enclosed in braces, followed by the closing
;;; angle-bracket (">") random objects have at the end.  This
;;; is used to distringuish random objects of the same type.

(defun finish-random (random-object)
  (write-string " {")
  (output-integer (%sp-make-fixnum random-object))
  (write-string "}>"))

;;; Functions Objects and other implmentation specific objects 
;;; are output here. 

(defun output-random (object)
  (write-string "#<")
  (cond ((functionp object)
	 (output-function-object object))
	((misc-type? object)
	 (write-string (misc-type-code object)))
	(t (write-string "Pointer into Hell, level ")
	   (output-integer (%sp-type object))))
  (finish-random object))