;;;-*-Mode: LISP; Package: CCL -*-
;;;
;;;   Copyright (C) 2009 Clozure Associates
;;;   Copyright (C) 1994-2001 Digitool, Inc
;;;   This file is part of Clozure CL.  
;;;
;;;   Clozure CL is licensed under the terms of the Lisp Lesser GNU Public
;;;   License , known as the LLGPL and distributed with Clozure CL as the
;;;   file "LICENSE".  The LLGPL consists of a preamble and the LGPL,
;;;   which is distributed with Clozure CL as the file "LGPL".  Where these
;;;   conflict, the preamble takes precedence.  
;;;
;;;   Clozure CL is referenced in the preamble as the "LIBRARY."
;;;
;;;   The LLGPL is also available online at
;;;   http://opensource.franz.com/preamble.html

;; L1-format.lisp
;
; This file contains the definition for SUB-FORMAT, the dispatching part
; of FORMAT. It also contains an interim definition for FORMAT and a few
; incompletely implemented directives.

(in-package "CCL")

(eval-when (eval compile #-bccl load)  ;Load-time as well so CCL can use it.
  (defmacro defformat (char name &rest def)
    `(progn
       (add-format-char ,char (nfunction ,name (lambda . ,def)))
       ',name))
  )

(defparameter *format-char-table* (let* ((x (make-array 128 :initial-element nil))) x))

(defun add-format-char (char def)
  (unless (and (characterp char) (%i< (%char-code char) 128))
    (report-bad-arg char 'standard-char))
  (setf (svref *format-char-table* (%char-code (char-upcase char))) def))

(proclaim '(special *format-original-arguments*   ;For ~*
                    *format-arguments*            ;For pop-format-arg
                    *format-control-string*       ;For ~?, ~{
                    *format-index*
                    *format-length*
                    *format-pprint*               ;~I,~W,~_,~:T seen?
                    *format-justification-semi*   ;~<..~:;..~> seen?
            ))

(defun pop-format-arg (&aux (args *format-arguments*))
  (if (null args)
    (format-error "Missing argument"))
  (progn
    (setq *format-arguments* (cdr args))
    (%car args)))
 
;SUB-FORMAT parses (a range of) the control string, finding the directives
;and applying them to their parameters.
;Implicit arguments to SUB-FORMAT: *format-control-string*, *format-arguments*,
;*format-original-arguments*, *standard-output*, *format-char-table*
;*format-control-string* must be a simple string.
;Directive functions' arglist should be (colon-p atsign-p &rest params)
;In addition when the directive is called, *format-index* and *format-length*
;are bound to start and end pos (in *format-control-string*) of the rest of the
; control string.  The directive may modify *format-index*, but not
; *format-control-string* and *format-length*, before returning.

(defun sub-format (stream *format-index* *format-length* &aux char)
  (prog* ((string (require-type *format-control-string* 'simple-string))
          (length *format-length*)
          (i *format-index*)
          (lastpos i))
    (declare (fixnum i length lastpos) (type simple-string string))
    (go START)
    EOF-ERROR
    (setq *format-index* *format-length*)
    (format-error "Premature end of control string")
    START
    (do* ()
         ((= i length) (unless (= i lastpos) 
                         (write-string string stream :start  lastpos :end i)))
      (setq char (schar string i) i (1+ i))
      (when (eq char #\~)
        (let* ((limit (the fixnum (1- i))))
          (unless (= limit lastpos) 
            (write-simple-string string stream  lastpos limit)))
        (let ((params nil) (fn) (colon nil) (atsign nil))
          (block nil
            (tagbody
              NEXT
              (if (= i length) (go EOF-ERROR))
              (setq char (schar string i) i (1+ i))
              (cond ((eq char #\#)
                     (push (list-length *format-arguments*) params))
                    ((eq char #\')
                     (if (= i length) (go EOF-ERROR))
                     (push (schar string i) params)
                     (incf i))
                    ((eq char #\,)
                     (push nil params)
                     (go NEXT))
                    ((or (eq char #\V) (eq char #\v))
                     (push (pop-format-arg) params))
                    ((or (eq char #\-) (eq char #\+) (digit-char-p char))
                     (let ((start (%i- i 1)) n)
                       (loop
                         (when (= i length) (go EOF-ERROR))
                         (unless (digit-char-p (schar string i)) (return))
                         (incf i))
                       (when (null (setq n (%parse-number-token string start i)))
                         (setq *format-index* i)
                         (format-error "Illegal parameter"))
                       (push n params)))
                    (t (return)))
              (if (= i length) (go EOF-ERROR))
              (setq char (schar string i) i (1+ i))
              (when (neq char #\,) (return))
              (go NEXT)))
          (cond ((eq char #\:) 
                 (if (= i length) (go EOF-ERROR))
                 (setq colon t char (schar string i) i (1+ i))
                 (when (eq char #\@)
                   (if (= i length) (go EOF-ERROR))                     
                   (setq atsign t char (schar string i) i (1+ i))))
                ((eq char #\@)
                 (if (= i length) (go EOF-ERROR))
                 (setq atsign t char (schar string i) i (1+ i))
                 (when (eq char #\:)
                   (if (= i length) (go EOF-ERROR))
                   (setq colon t char (schar string i) i (1+ i)))))
          (setq *format-index* (%i- i 1))
          (if (setq fn (svref *format-char-table* (%char-code (char-upcase char))))
            (apply fn stream colon atsign (nreverse params))
            (format-error "Unknown directive"))
          (setq i (%i+ *format-index* 1)
                lastpos i))))))


#||
(eval-when (load)
  ;The non-consing version.
(defun sub-format (stream *format-index* *format-length*)
  (declare (special *format-index* *format-length*))
  (old-lap-inline (stream)
    (preserve_regs #(asave0 asave1 dsave0 dsave1 dsave2))
    (defreg Control-string asave0 Index dsave0 Length dsave1 NumParams dsave2 Stream asave1)
    (move.l acc Stream)
    (move.l (special *format-index*) Index)       ; *format-index*
    (move.l (special *format-length*) Length)      ; *format-length*
    (specref *format-control-string*)
    (move.l acc Control-string)

    ;Make sure everything is in bounds, so don't have to worry about
    ;boxing, bounds checking, etc.
start
    (movereg Control-string arg_z)
    (jsr_subprim $sp-length)
    (ccall <= '0 Index Length acc)
    (cmp.l nilreg acc)
    (beq done)
    (move.l Index db)
    (loop#
      (if# (eq Length Index)
        (cmp.l db Index)
        (beq done)
        (ccall 'stream-write-string Stream Control-string db Index)
        (bra done))
      (move.l Index da)
      (getint da)
      (move.l ($ $t_imm_char 0) acc)
      (move.b (Control-string da.l $v_data) acc)
      (add.l (fixnum 1) Index)
      (cmp.b ($ #\~) acc)
      (beq tilde))

nextchar
    (if# (eq Length Index)
      (move.l '"Premature end of format control string" arg_z)
      (add.w ($ 4) sp)                  ; flush internal bsr.
      (bra error))
    (move.l Index da)
    (getint da)
    (move.b (Control-string da.l $v_data) acc)
    (add.l (fixnum 1) Index)
    (if# (and (ge (cmp.b ($ #\a) acc)) (le (cmp.b ($ #\z) acc)))
      (sub.b ($ 32) acc))
    (rts)

tilde
    (move.l Index da)
    (sub.l (fixnum 1) da)
    (if# (not (eq da db))      
      (ccall 'stream-write-string Stream Control-string db da))
    (vpush Stream)
    (vpush nilreg)             ;assume no :
    (vpush nilreg)             ;assume no @
    (move.l (fixnum 3) NumParams)
do-param
    (bsr nextchar)
    (if# (or (eq (cmp.b ($ #\+) acc))
             (eq (cmp.b ($ #\-) acc))
             (and (ge (cmp.b ($ #\0) acc)) (le (cmp.b ($ #\9) acc))))
      (move.l Index da)
      (sub.l (fixnum 1) da)
      (vpush da)
      (prog#
       (bsr nextchar)
       (until# (or (lt (cmp.b ($ #\0) acc)) (gt (cmp.b ($ #\9) acc)))))
      (sub.l (fixnum 1) Index)   ;unread the non-digit char
      (ccall %parse-number-token Control-string vsp@+ Index)
      (cmp.l nilreg acc)
      (bne push-param)
      (move.l '"Illegal format parameter" arg_z)
      (bra error))

    (if# (eq (cmp.b ($ #\#) acc))
      (move.l (special *format-arguments*) acc)
      (jsr_subprim $sp-length)
      (bra push-param))

    (if# (eq (cmp.b ($ #\') acc))
      (bsr nextchar)
      (move.l ($ $t_imm_char 0) acc)
      (move.b (Control-string da.l $v_data) acc)  ;Get the non-uppercased version...
      (swap acc)
      (bra push-param))

    (if# (eq (cmp.b ($ #\,) acc))
      (sub.l (fixnum 1) Index)   ;Re-read the comma.
      (move.l nilreg acc)
      (bra push-param))

    (if# (eq (cmp.b ($ #\V) acc))
      (ccall 'pop-format-arg)
      ;(bra push-param)
     push-param
      (vpush acc)
      (add.l (fixnum 1) NumParams)
      (bsr nextchar)
      (cmp.b ($ #\,) acc)
      (beq do-param))

    (move.l NumParams nargs)
    (vscale.l nargs)
    (cmp.b ($ #\:) acc)
    (if# eq
      (bsr nextchar)
      (cmp.b ($ #\@) acc)
      (bne @a)
      (move.l (a5 $t) (vsp nargs.w -12))
     else#
      (cmp.b ($ #\@) acc)
      (bne @b)
      (move.l (a5 $t) (vsp nargs.w -12))
      (bsr nextchar)
      (cmp.b ($ #\:) acc)
      (bne @b))
    (bsr nextchar)
@a  (move.l (a5 $t) (vsp nargs.w -8))
@b  (moveq 127 da)
    (and.w acc da)
    (bif (ne (cmp.b da acc)) nofun)
    (lsl.w 2 da)
    (move.l (special *format-char-table*) atemp0)
    (move.l (atemp0 da.w $v_data) atemp0)
    (cmp.l atemp0 nilreg)
    (beq nofun)
    (move.l Index da)
    (sub.l (fixnum 1) da)
    (move.l da (special *format-index*))
    (move.l NumParams nargs)
    (vscale.l nargs)                    ; at least 3 args.
    (movem.l vsp@+ #(arg_z arg_y arg_x))
    (jsr_subprim $sp-funcall)
    (specref '*format-index*)
    (add.l (fixnum 1) acc)
    (move.l acc Index)
    (bra start)

nofun
    (move.l '"Unknown format directive" acc)
error
    (move.l Index (special *format-index*))
    (fsymevalapply 'format-error 1)

done
    (restore_regs)
    ))
) ;end of eval-when (load)

||#

;;;Interim definitions

;;;This function is shadowed by CCL in order to use ~{ to print error messages.
(fset 'format 
      (nlambda bootstrapping-format (stream control-string &rest format-arguments)
        (declare (dynamic-extent format-arguments))
        (block format
          (when (null stream)
            (return-from format 
              (with-output-to-string (x)
                (apply #'format x control-string format-arguments))))
          (if (eq stream t)
            (setq stream *standard-output*)
            (unless (streamp stream) (report-bad-arg stream 'stream)))
          (if (functionp control-string)
            (apply control-string stream format-arguments)
            (progn
              (setq control-string (ensure-simple-string control-string))
              (let* ((*format-original-arguments* format-arguments)
                     (*format-arguments* format-arguments)
                     (*format-control-string* control-string))
                (catch 'format-escape
                  (sub-format stream 0 (length control-string)))
                nil))))))

(fset 'format-error
      (nlambda bootstrapping-format-error (&rest args)
        (format t "~&FORMAT error at position ~A in control string ~S "
                *format-index* *format-control-string*)
        (apply #'error args)))

(defun format-no-flags (colon atsign)
  (cond ((and colon atsign)
         (format-error "Flags not allowed"))
        (colon
         (format-error ": flag not allowed"))
        (atsign
         (format-error "@ flag not allowed"))))

;Redefined later
(defformat #\A format-a (stream colon atsign)
   (declare (ignore colon atsign))
   (princ (pop-format-arg) stream))

;Redefined later
(defformat #\S format-s (stream colon atsign)
  (declare (ignore colon atsign))
  (prin1 (pop-format-arg) stream))

;Redefined later
(defformat #\^ format-escape (stream colon atsign)
  (declare (ignore stream colon atsign))
  (throw 'format-escape t))

;Final version
(defformat #\% format-% (stream colon atsign &optional repeat-count)
  (format-no-flags colon atsign)
  (cond ((or (not repeat-count)
             (and (fixnump repeat-count)
                  (> repeat-count -1)))
         (dotimes (i (or repeat-count 1)) (declare (fixnum i)) (terpri stream)))
        (t (format-error "Bad repeat-count."))))

;Final version
(defformat #\& format-& (stream colon atsign &optional repeat-count)
  (format-no-flags colon atsign)
  (cond ((or (not repeat-count)
             (and (fixnump repeat-count)
                  (> repeat-count -1)))
         (unless (eq repeat-count 0)
           (fresh-line stream)
           (dotimes (i (1- (or repeat-count 1))) (declare (fixnum i)) (terpri stream))))
        (t (format-error "Bad repeat-count."))))

;Final version
(defformat #\~ format-~ (stream colon atsign &optional repeat-count)
  (format-no-flags colon atsign)
  (cond ((or (not repeat-count)
             (and (fixnump repeat-count)
                  (> repeat-count -1)))
         (dotimes (i (or repeat-count 1)) (declare (fixnum i)) (write-char #\~ stream)))
        (t (format-error "Bad repeat-count."))))

;Final version
(defformat #\P format-p (stream colon atsign)
  (when colon
     (let ((end *format-arguments*) (list *format-original-arguments*))
        (tagbody loop
           (if list
             (when (neq (cdr list) end)
               (setq list (%cdr list))
               (go loop))
             (format-error "No previous argument")))
        (setq *format-arguments* list)))
   (%write-string (if (eq (pop-format-arg) 1)
                    (if atsign "y" "")
                    (if atsign "ies" "s"))
                  stream))

;Final version
(defformat #\* format-* (stream colon atsign &optional count)
  (declare (ignore stream)(special *circularity-hash-table*))
  (let* ((orig *format-original-arguments*)
         (where (- (list-length orig)   ; will error if args circular
                   (list-length *format-arguments*)))
         (to (if atsign 
               (progn
                 (format-no-flags colon nil)
                 (or count 0)) ; absolute
               (progn
                 (when (null count)(setq count 1))
                 (when colon (setq count (- count)))
                 (%i+ where count))))
         (args (nthcdr-no-overflow to orig)))
    ; avoid bogus circularity indication
    (when (and nil (consp args) (<= to where) *circularity-hash-table*)
      ; copy only from to thru where in case  some real shared structure
      (let ((l args) new)
        (dotimes (i (1+  (- where to)))
          (declare (fixnum i))
          (push (car l) new)
          (setq l (cdr l)))
        (setq args (nreconc new (nthcdr (1+ where) orig))))) ;(copy-list args)))
    (setq *format-arguments* args)))

; Redefined later.
(defformat #\Newline format-newline (&rest ignore)
  (declare (ignore ignore))
  (do* ((i *format-index* (1+ i))
        (s *format-control-string*)
        (n *format-length*))
       ((or (= i n)
            (not (whitespacep (schar s i))))
        (setq *format-index* (1- i)))))

(defun nthcdr-no-overflow (count list)
  (if (or (> count (list-length list)) (< count 0))
    (format-error "non-existent target for ~~*")
    (nthcdr count list)))

;Redefined later
(defformat #\X format-x (stream colon atsign)
  (declare (ignore colon atsign))
  (let* ((*print-base* 16.)
         (*print-radix* nil))
    (prin1 (pop-format-arg) stream)))

;Redefined later
(defformat #\D format-d (stream colon atsign &rest ignore)
  (declare (ignore colon atsign ignore))
  (let* ((*print-base* 10.)
         (*print-radix* nil))
    (prin1 (pop-format-arg) stream)))
