; .EnTete "Le-Lisp (c) version 15.2" " " "La fonction FORMAT"
; .EnPied " " "%" " "
; .Chapitre 16 "La fonction FORMAT"             
;
; .Centre "*****************************************************************"
; .Centre " Ce fichier est en lecture seule hors du projet ALE de l'INRIA.  "
; .Centre " Il est maintenu par ILOG SA, 2 Avenue Gallie'ni, 94250 Gentilly "
; .Centre " (c) Le-Lisp est une marque de'pose'e de l'INRIA                 "
; .Centre "*****************************************************************"

; .Centre "$Header: format.ll,v 4.1 88/01/13 12:19:42 kuczynsk Rel $"

(unless (>= (version) 15.2)
        (error 'load 'erricf 'format))

; Tous les symboles pre'ce'de's de : seront cre'e's dans le package FORMAT.

(defvar #:sys-package:colon 'format)

(add-feature 'format)

; .Section "La fonction d'appel"

(de prinf (:f-string . :args)
    (ifn (stringp :f-string) (:error 'format :f-string))
    (mapc 'princn (:format (pname :f-string) :args ())))

(de format (:where :f-string . :args)
    (ifn (stringp :f-string) (:error 'format :f-string))
    (let ((:result (:format (pname :f-string) :args ())))
        (cond
            ((null :where) (:string :result))
            ((or (fixp :where) (eq :where t))
             (when (eq :where t) (setq :where ()))
             (with ((outchan :where)) (mapc 'princn :result) ()))
            (t (:error 'format :where)))))

; .Section "Les fonctions internes"

(de :format (:format :args :r)
    (let ((:f :format)(:l :args))
        (tag format (tag hat (:format1))))
    :r)

(de :format1 ()
    (let ((:char (nextl :f)))
        (cond
            ((null :char) (exit format))
            ((eq :char #/~)
             (let ((:par1)(:par2)(:par3)(:par4)
                   (:par5)(:par6)(:par7)
                   (:at-sign)(:colon)(:par-n 1))
                (:tilde-decode)))
            (t (newr :r :char))))
    (:format1))

(de :tilde-decode ()
    (let ((:c (nextl :f)))
        (selectq :c
            ((#/0 #/1 #/2 #/3 #/4 #/5 #/6 #/7 #/8 #/9 #/- #/+)
             (set (symbol 'format (concat 'par :par-n)) (:read-n (list :c)))
             (:tilde-decode))
            (#/'
             (set (symbol 'format (concat 'par :par-n)) (nextl :f))
             (:tilde-decode))
            ((#/v #/V)
             (set (symbol 'format (concat 'par :par-n)) (nextl :l))
             (:tilde-decode))
            (#/#
             (set (symbol 'format (concat 'par :par-n)) (length :l))
             (:tilde-decode))
            (#/, (incr :par-n) (:tilde-decode))
            (#/@ (setq :at-sign t) (:tilde-decode))
            (#/: (setq :colon t) (:tilde-decode))
            (#/A (:format-ascii (nextl :l)))
            (#/S (:format-sexpr (nextl :l)))
            (#/D (:format-decimal (nextl :l)))
            (#/B (:format-binary (nextl :l)))
            (#/O (:format-octal (nextl :l)))
            (#/X (:format-hexa (nextl :l)))
            (#/R (:format-radix (nextl :l)))
            (#/P (:format-plural))
            (#/C (:format-char (nextl :l)))
            (#/E (:format-e-float (nextl :l)))
            (#/F (:format-f-float (nextl :l)))
            (#/G (:format-g-float (nextl :l)))
            (#/% (:format-nl))
            (#\lf (:format-eol))
            (#/~ (:format-tilde))
            (#/T (:format-tab))
            (#/* (:format-ignore))
            (#/? (:format-indirect))
            (#/[ (:format-cond))
            (#/{ (:format-iter ()))
            (#/↑ (:format-hat))
            (#/] (:error 'format
			 #- #:system:foreign-language "~] hors contexte"
			 #+ #:system:foreign-language "~] out of context"))
            (#/; (:error 'format
	                 #- #:system:foreign-language "~; hors contexte"
	                 #+ #:system:foreign-language "~; out of context"))
            (#/} (:error 'format
			 #- #:system:foreign-language "~} hors contexte"
			 #+ #:system:foreign-language "~} out of context"))
            (#/) (:error 'format
			 #- #:system:foreign-language "~) hors contexte"
			 #+ #:system:foreign-language "~) out of context"))
            (#/> (:error 'format
			 #- #:system:foreign-language "~> hors contexte"
			 #+ #:system:foreign-language "~> out of context"))
            (t (:error 'format (string (list #/~ :c)))))))

(de :pad (:val :wide :side :padchar)
    (let ((:length (length :val)))
        (if (>= :length :wide) :val
            (if :side
                (nconc :val (makelist (- :wide :length) :padchar))
                (nconc (makelist (- :wide :length) :padchar) :val)))))

(de :pad-4 (:val :wide :side :padchar :colinc :minpad)
    (setq :val (if :side (nconc :val (makelist :minpad :padchar))
                         (nconc (makelist :minpad :padchar) :val)))
    (:pad-4-loop (length :val)))

(de :pad-4-loop (:length)
    (if (<= :wide :length) :val
        (setq :val (if :side (nconc :val (makelist :colinc :padchar))
                             (nconc (makelist :colinc :padchar) :val)))
        (:pad-4-loop (length :val))))


; .Section "Les diffe'rents spe'cialistes des FORMATS"

(de :format-pad (:val :par-1 :par-2)
    (setq :r (nconc :r (ifn :par-1 :val
                            (:pad :val :par-1 :at-sign
                                  (if :par-2 :par-2 #\sp))))))

(de :format-ascii (:arg)
    (let ((#:system:print-for-read ()))
        (if (or :par3 :par4)
            (setq :r (nconc :r (:pad-4 (explode :arg) (if :par1 :par1 0)
                                       :at-sign (if :par4 :par4 #\sp)
                                       (if :par2 :par2 1)
                                       (if :par3 :par3 0))))
            (:format-pad (explode :arg) :par1 :par2))))

(de :format-sexpr (:arg)
    (let ((#:system:print-for-read t))
        (if (or :par3 :par4)
            (setq :r (nconc :r (:pad-4 (explode :arg) (if :par1 :par1 0)
                                       :at-sign (if :par4 :par4 #\sp)
                                       (if :par2 :par2 1)
                                       (if :par3 :par3 0))))
            (:format-pad (explode :arg) :par1 :par2))))

(de :format-decimal (:arg)
    (with ((obase 10))
        (let ((#:system:print-for-read ()))
             (:format-pad (explode :arg) :par1 :par2))))

(de :format-binary (:arg)
    (with ((obase 2))
        (let ((#:system:print-for-read ()))
             (:format-pad (explode :arg) :par1 :par2))))

(de :format-octal (:arg)
    (with ((obase 8))
        (let ((#:system:print-for-read ()))
             (:format-pad (explode :arg) :par1 :par2))))

(de :format-hexa (:arg)
    (with ((obase 16))
        (let ((#:system:print-for-read ()))
             (:format-pad (explode :arg) :par1 :par2))))

(de :format-radix (:arg)
    (ifn (fixp :par1) (:error ':format-radix "pas de base"))
    (with ((obase :par1))
        (let ((#:system:print-for-read ()))
             (:format-pad (explode :arg) :par2 :par3))))

(de :format-plural ()
    (let ((:arg (ifn :colon (nextl :l)
                     (nth (- (length :args) (length :l) 1) :args))))
        (if (eq :arg 1)
            (if :at-sign (setq :r (nconc1 :r #/y)))
            (setq :r (nconc :r
                            (if :at-sign (list #/i #/e #/s) (list #/s)))))))

(de :format-char (:arg)
    (ifn (asciip :arg) (:error ':format-char :arg))
    (cond
        ((and :at-sign :colon)
	 (:error ':format-char
		 #- #:system:foreign-language "pas @ et : ensemble"
		 #+ #:system:foreign-language "@ and : not together"))
        (:at-sign (setq :r (nconc :r
            (cond
                ((= :arg #\del) (append '#"#\del" ()))
                ((> :arg #\sp) (list #/# #// :arg))
                ((= :arg #\sp) (append '#"#\sp" ()))
                (t (list #/# #/↑ (logor #$40 :arg)))))))
        (:colon (setq :r
            (let ((:name (cassq :arg :char-names)))
                (if :name (nconc :r (pname :name))
                          (nconc1 :r :arg)))))
        (t (setq :r (nconc1 :r :arg)))))

(defvar :char-names ())

(for (i 0 1 31)
    (newl :char-names (cons i (concat 'control- (ascii (logor #$40 i))))))

(progn (newl :char-names '(0 . null))
       (newl :char-names '(7 . bell))
       (newl :char-names '(8 . back-space))
       (newl :char-names '(9 . tab))
       (newl :char-names '(10 . line-feed))
       (newl :char-names '(13 . return))
       (newl :char-names '(27 . escape))
       (newl :char-names '(32 . space))
       (newl :char-names '(127 . delete)))

(de :float-pad (:wide :ovfchar :padchar :val)
    (let ((:length (length :val)))
        (setq :r
              (nconc :r
                   (ifn :wide :val
                        (if (and :ovfchar (< :wide :length))
                            (makelist :wide :ovfchar)
                            (nconc (makelist (- :wide :length)
                                             (if :padchar :padchar #\sp))
                                   :val)))))))

(de :float-digits (:count)
    (let* ((:x (* :f (power 10 (- 0 1 :exp))))
	   (:y (* .5 (power 10 (- :count))))
	   (:rest (+ :x :y))
	   (:list))
        (when (> (fix :rest)(fix :x)) ; Au cas ou` l'arrondi nous engendre un
	      (setq :rest :x)) ;digit de plus avant la virgule(ex: 9.9 -> 10.0)
        (while (> :count 0)
               (let ((:fix (fix (* :rest 1e+4))))
                   (setq :list (nconc :list (:next-digits (explode :fix))))
                   (setq :rest (- (* :rest 1e+4) :fix))
                   (setq :count (- :count 4))))
        :list))

(de :next-digits (:sublist)
    (let ((:length (length :sublist)))
        (nconc (makelist (- 4 :length) #/0) :sublist)))

(de :format-f-float (:arg)
    (ifn (numberp :arg)
         (let ((:par1 :par1)(:par2 :par5))
             (:format-decimal :arg))
         (ifn (floatp :arg) (setq :arg (float :arg)))
         (if :par3 (setq :arg (* :arg (power 10 :par3))))
         (:float-pad :par1 :par4 :par5
             (let ((:rr (if (< :arg 0) (list #/-) (if :at-sign (list #/+))))
                   (:f (abs :arg)))
                (if (or :par1 :par2)
                    (slet ((:exp (if (= :f 0) 0 (fix (log10 :f))))
                           (:par1 (or :par1 100))
                           (:par2
                            (or :par2
                                (max 0 (- :par1 2 (max :exp 0)
                                          (if (or :at-sign (< :arg 0))
                                              1 0)))))
                           (:digits (:float-digits (+ :par2 :exp 1))))
                       (:float-f) :rr)
                    (explode :f))))))

(de :float-f ()
    (if (< :exp 0)
        (let ((:zero (min (- -1 :exp) :par2)))
            (ifn (and (= (- :par1 :par2) 1) (not :at-sign) (>= :arg 0))
                 (newr :rr #/0))
            (newr :rr #/.)
            (if (zerop :f)
                (repeat :zero (newr :rr #/0))
                (cond
                    ((<> :zero :par2)
                     (repeat :zero (newr :rr #/0))
                     (repeat (- :par2 :zero) (newr :rr (nextl :digits))))
                    (t (repeat (1- :zero) (newr :rr #/0)) (newr :rr #/1)))))
        (repeat (1+ :exp) (newr :rr (nextl :digits)))
        (newr :rr #/.)
        (repeat :par2 (newr :rr (nextl :digits)))))

(de :format-e-float (:arg)
    (ifn (numberp :arg)
         (let ((:par1 :par1)(:par2 :par6))
             (:format-decimal :arg))
         (ifn (floatp :arg) (setq :arg (float :arg)))
         (ifn :par4 (setq :par4 1))
         (:float-pad :par1 :par5 :par6
             (let ((:rr (if (< :arg 0) (list #/-) (if :at-sign (list #/+))))
                   (:f (abs :arg)))
                (if (or :par1 :par2 :par3)
                    (let ((:exp (if (= :f 0) 0 (floor (log10 :f))))
                          (:par1 (or :par1 100))
                          (:exp-r)
                          (:exp-l))
                        (:float-exp (- :exp :par4 -1))
                        (setq :exp-l (length :exp-r))
                        (if (= :exp-l :par1) :exp-r
                            (:float-e (- :par1 :exp-l (length :rr)))
                            (nconc :rr :exp-r)))
                    (explode :f))))))

(de :float-exp (:t-exp)
    (newr :exp-r (or :par7 #/e))
    (newr :exp-r (if (>= :t-exp 0) #/+ #/-))
    (slet ((:nb-list (pname (abs :t-exp)))(:length (length :nb-list)))
        (if (and :par5 :par3 (< :par3 :length))
            (setq :exp-r (makelist :par1 :par5))
            (setq :exp-r (nconc :exp-r
                                (if :par3 (makelist (- :par3 :length) #/0))
                                :nb-list)))))

(de :float-e (:r-length)
    (ifn :par2 (setq :par2 (- :r-length 2)))
    (cond
        ((zerop :par4)
         (setq :par2 (max :par2 0))
         (let ((:digits (:float-digits :par2)))
             (:float-e-1 0 0 :par2)))
        ((> :par4 0)
         (setq :par2 (max :par2 (1- :par4)))
         (let ((:digits (:float-digits (1+ :par2))))
             (:float-e-1 :par4 0 (- :par2 -1 :par4))))
        (t (setq :par2 (max :par2 (- 1 :par4)))
         (let ((:digits (:float-digits (+ :par2 :par4))))
             (:float-e-1 0 (abs :par4) (+ :par2 :par4))))))

(de :float-e-1 (:before :zero :after)
    (repeat :before (newr :rr (nextl :digits)))
    (if (and (zerop :before) (<> :r-length (+ 1 :zero :after)))
        (newr :rr #/0))
    (newr :rr #/.)
    (if (> :zero 0) (setq :rr (nconc :rr (makelist :zero #/0))))
    (repeat :after (newr :rr (nextl :digits))))

(de :format-g-float (:arg)
    (ifn (numberp :arg)
         (let ((:par1 :par1)(:par2 :par6))
             (:format-decimal :arg))
         (ifn (floatp :arg) (setq :arg (float :arg)))
         (slet ((:n (if (zerop :arg) 0 (1+ (floor (log10 (abs :arg))))))
                (:ee (ifn :par3 4 (+ :par3 2)))
                (:ww (ifn :par1 () (- :par1 :ee)))
                (:q (- :ww 2))
                (:d (if :par2 :par2 (max :q (min :n 7))))
                (:dd (- :d :n)))
            (if (or (< :dd 0) (> :dd :d))
                (:format-e-float :arg)
                (let ((:par1 :ww)(:par2 :dd)(:par3)(:par4 :par5)(:par5 :par6))
                    (:format-f-float :arg))
                (setq :r (nconc :r (makelist :ee #\sp)))))))

(de :format-nl ()
    (ifn :par1 (setq :par1 1))
    (setq :r (nconc :r (makelist :par1 #\lf))))

(de :format-eol ()
    (cond
        ((and :at-sign :colon) (:error ':format-eol "pas @ et : ensemble"))
        (:at-sign (setq :r (nconc1 :r #\lf))
         (while (eq (typecn (car :f)) 'csep) (nextl :f)))
        (:colon)
        (t (while (eq (typecn (car :f)) 'csep) (nextl :f)))))

(de :format-tilde ()
    (ifn :par1 (setq :par1 1))
    (setq :r (nconc :r (makelist :par1 #/~))))

(de :format-tab ()
    (let ((:back :r))
        (while (memq #\lf :back) (setq :back (cdr (memq #\lf :back))))
        (setq :back (length :back))
        (ifn :par1 (setq :par1 1))
        (ifn :par2 (setq :par2 1))
        (let ((:n)(:base (if :at-sign 0 :par1))(:pos))
             (if :at-sign
                 (setq :n :par1 :pos (+ :back :par1))
                 (if (< :back :par1)
                     (setq :n (- :par1 :back) :pos :par1)
                     (setq :n 0 :pos :back)))
             (let ((:mod (modulo (- :pos :base) :par2)))
                (ifn (zerop :mod)
                     (setq :n (+ :n (- :par2 :mod)))))
             (setq :r (nconc :r (makelist :n #\sp))))))

(de :format-ignore ()
    (ifn :par1 (setq :par1 (if :at-sign 0 1)))
    (cond
        ((and :at-sign :colon) (:error ':format-ignore "pas @ et : ensemble"))
        (:at-sign (setq :l (nthcdr :par1 :args)))
        (:colon (let ((:l1 (length :args)) (:l2 (length :l)))
            (setq :l (nthcdr (- :l1 :l2 :par1) :args))))           
        (t (setq :l (nthcdr :par1 :l)))))

(de :format-indirect ()
    (slet ((:s (nextl :l))(:format (pname :s)))
        (ifn (stringp :s) (:error ':format-indirect :s))
        (tag hat
            (if :at-sign (:format-ind-at) (:format-ind-list (nextl :l))))))

(de :format-ind-at ()
    (let ((:f :format)) (:format-ind-at1 (nextl :f))))

(de :format-ind-at1 (:char)
    (cond
        ((null :char))
        ((eq :char #/~)
         (let ((:par1)(:par2)(:par3)(:par4)
               (:par5)(:par6)(:par7)
               (:at-sign)(:colon)(:par-n 1))
            (:tilde-decode) (:format-ind-at1 (nextl :f))))
        (t (newr :r :char) (:format-ind-at1 (nextl :f)))))

(de :format-ind-list (:args)
    (ifn (listp :args) (:error ':format-indirect :args))
    (let ((:f :format)(:l :args))
        (tag format (tag hat (:format1)))))

(de :format-cond ()
    (cond
        ((and :at-sign :colon) (:error ':format-cond "pas @ et : ensemble"))
        (:at-sign (setq :par1 (car :l))
         (cond
             ((not :par1) (:search-cond (nextl :f) 0 ()) (nextl :l))
             (t (slet ((:format (:search-cond (nextl :f) 0 ()))(:f :format))
                    (tag format (:format1))))))
        (:colon (nextl :l :par1)
         (slet ((:format (:search-cond (nextl :f) (if :par1 1 0) ()))
                (:f :format))
             (tag format (:format1))))
        (t (ifn :par1 (nextl :l :par1))
         (slet ((:format (:search-cond (nextl :f) :par1 ()))(:f :format))
                (tag format (:format1))))))

(de :search-cond (:c :n :ff)
    (ifn :c (:error ':format-cond :f))
    (if (zerop :n)
        (selectq :c
            (#/~ (let ((:cc (nextl :f)))
                (selectq :cc
                    (() (:error ':format-cond :f))
                    (#/] :ff)
                    (#/; (prog1 :ff (:search-cond (nextl :f) -1 ())))
                    (#/: (if (eq (car :f) #/;)
                             (prog1 :ff (:search-cond (nextl :f) -1 ()))
                             (:search-cond (nextl :f) :n
                                           (nconc :ff (list :c :cc)))))
                    (t (:search-cond (nextl :f) :n
                                     (nconc :ff (list :c :cc)))))))
            (t (:search-cond (nextl :f) :n (nconc1 :ff :c))))
        (ifn (eq :c #/~) (:search-cond (nextl :f) :n ())
             (nextl :f :c)
             (selectq :c
                (() (:error ':format-cond :f))
                (#/] ())
                (#/; (:search-cond (nextl :f) (1- :n) ()))
                (#/: (nextl :f :c)
                 (:search-cond (nextl :f) (if (eq :c #/;) 0 :n) ()))
                (t (:search-cond (nextl :f) :n ()))))))

(de :format-iter (:iter-colon)
    (let ((:iter-n (if :par1 :par1 -1))
          (:iter-n-first)
          (:format (:search-iter (nextl :f) () ()))
          (:f))
        (unless (zerop :iter-n)
                (unless :format
                    (setq :format (nextl :l))
                    (ifn (stringp :format) (:error ':format-iter :format))
                    (setq :format (pname :format)))
                (setq :iter-n-first :iter-n :f :format)
                (cond
                    ((and :at-sign :colon) (tag colon-hat (:iter-colon :l)))
                    (:at-sign (tag hat (:iter)))
                    (:colon (tag colon-hat (:iter-colon (nextl :l))))
                    (t (let ((:args (car :l))(:l (nextl :l)))
                        (tag hat (:iter))))))))

(de :search-iter (:c :ff :cc)
    (cond
        ((null :c)
	 (:error ':format-iter
		 #- #:system:foreign-language "pas referme"
		 #+ #:system:foreign-language "not closed"))
        ((eq :c #/~) (nextl :f :cc)
         (cond
             ((eq :cc #/}) :ff)
             ((and (eq :cc #/:) (eq (car :f) #/}))
              (setq :iter-colon t) (nextl :f) :ff)
             (t (:search-iter (nextl :f) (nconc :ff (list :c :cc)) ()))))
        (t (:search-iter (nextl :f) (nconc1 :ff :c) ()))))

(de :iter ()
    (if (null :format) (exit hat))
    (if (and (null :l) (not :iter-colon)) (exit hat))
    (:iter-loop (nextl :f)))

(de :iter-loop (:char)
    (cond
        ((zerop :iter-n) (exit hat))
        ((null :char)
         (if (null :l) (exit hat))
         (decr :iter-n) (setq :f :format) (:iter-loop (nextl :f)))
        ((eq :char #/~)
         (let ((:par1)(:par2)(:par3)(:par4)(:par5)(:par6)(:par7)
               (:at-sign)(:colon)(:par-n 1))
            (:tilde-decode)))
        (t (newr :r :char)))
    (:iter-loop (nextl :f)))

(de :iter-colon (:sub-args)
    (if (null :format) (exit colon-hat))
    (:iter-colon-loop (car :sub-args) (nextl :sub-args)))

(de :iter-colon-loop (:args :l)
    (if (and (null :l) (null :sub-args)
             (not (and (= :iter-n :iter-n-first) :iter-colon)))
        (exit colon-hat))
    (if (zerop :iter-n) (exit colon-hat))
    (decr :iter-n)
    (tag hat (tag format (let ((:f :format)) (:format1))))
    (:iter-colon-loop (car :sub-args) (nextl :sub-args)))

(de :format-hat ()
    (if (null :par1) (setq :par1 (length :l)))
    (if (or (and (null :par2) (null :par3) (zerop :par1))
            (and :par2 (null :par3) (= :par1 :par2))
            (and :par2 :par3 (or (<= :par1 :par2) (<= :par2 :par3))))
        (if :colon (exit colon-hat) (exit hat))))

(de :read-n (:l)
    (let ((:c (car :f)))
        (cond
           ((digitp :c) (nextl :f) (:read-n (nconc1 :l :c)))
           (t (implode :l)))))

(de :string (:l)
    (slet ((:n (length :l))(:s (makestring :n 32))(:l (nreverse :l)))
        (while (> :n 0) (decr :n) (chrset :n :s (nextl :l)))
        :s))

(de :error (a b) (error "Format" a b))