;;; Ikarus Scheme -- A compiler for R6RS Scheme.
;;; Copyright (C) 2006,2007,2008  Abdulaziz Ghuloum
;;; 
;;; This program is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License version 3 as
;;; published by the Free Software Foundation.
;;; 
;;; This program is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;;; General Public License for more details.
;;; 
;;; You should have received a copy of the GNU General Public License
;;; along with this program.  If not, see <http://www.gnu.org/licenses/>.


(library (ikarus writer)
  (export write display format printf fprintf print-error print-unicode print-graph
          put-datum)
  (import 
    (rnrs hashtables)
    (ikarus system $chars)
    (ikarus system $strings)
    (ikarus system $vectors)
    (ikarus system $fx)
    (ikarus system $pairs)
    (ikarus system $symbols)
    (ikarus system $bytevectors)
    (ikarus system $transcoders)
    (only (ikarus system $foreign) pointer? pointer->integer)
    (only (ikarus.pretty-formats) get-fmt)
    (except (ikarus) 
      write display format printf fprintf print-error print-unicode print-graph
      put-datum))

  (define print-unicode
    (make-parameter #t))

  (define char-table ; first nonprintable chars
    '#("nul" "x1" "x2" "x3" "x4" "x5" "x6" "alarm"
       "backspace" "tab" "linefeed" "vtab" "page" "return" "xE" "xF" 
       "x10" "x11" "x12" "x13" "x14" "x15" "x16" "x17" 
       "x18" "x19" "x1A" "esc" "x1C" "x1D" "x1E" "x1F" 
       "space"))

  (define write-positive-hex-fx
    (lambda (n p)
      (unless ($fx= n 0)
        (write-positive-hex-fx ($fxsra n 4) p)
        (let ([n ($fxlogand n #xF)])
          (cond
            [($fx<= n 9)
             (write-char ($fixnum->char 
                           ($fx+ ($char->fixnum #\0) n)) 
                         p)]
            [else
             (write-char ($fixnum->char
                           ($fx+ ($char->fixnum #\A) ($fx- n 10)))
                         p)])))))

  (define write-character
    (lambda (x p m)
      (if m 
          (let ([i ($char->fixnum x)])
            (write-char #\# p)
            (cond
             [(fx< i (vector-length char-table))
              (write-char #\\ p)
              (write-char* (vector-ref char-table i) p)]
             [(fx< i 127)
              (write-char #\\ p)
              (write-char x p)]
             [(fx= i 127) 
              (write-char #\\ p)
              (write-char* "delete" p)]
             [(and (print-unicode) (unicode-printable-char? x))
              (write-char #\\ p)
              (write-char x p)]
             [else
              (write-char #\\ p)
              (write-char #\x p)
              (write-positive-hex-fx i p)]))
          (write-char x p))))

  (define write-list
    (lambda (x p m h i)
      (cond
       [(and (pair? x) 
             (or (not (hashtable-ref h x #f))
                 (fxzero? (hashtable-ref h x 0))))
        (write-char #\space p)
        (write-list (cdr x) p m h 
          (writer (car x) p m h i))]
       [(null? x) i]
       [else
        (write-char #\space p)
        (write-char #\. p)
        (write-char #\space p)
        (writer x p m h i)])))

  (define write-vector
    (lambda (x p m h i)
      (write-char #\# p)
      (write-char #\( p)
      (let ([n (vector-length x)])
        (let ([i 
               (cond
                 [(fx> n 0)
                  (let f ([idx 1] [i (writer (vector-ref x 0) p m h i)])
                    (cond
                      [(fx= idx n)
                       i]
                      [else
                       (write-char #\space p)
                       (f (fxadd1 idx)
                          (writer (vector-ref x idx) p m h i))]))]
                 [else i])])
           (write-char #\) p)
           i))))

  (define write-bytevector
    (lambda (x p m h i)
      (write-char #\# p)
      (write-char #\v p)
      (write-char #\u p)
      (write-char #\8 p)
      (write-char #\( p)
      (let ([n ($bytevector-length x)])
        (when (fx> n 0)
          (write-fixnum ($bytevector-u8-ref x 0) p)
          (let f ([idx 1] [n n] [x x] [p p])
            (unless ($fx= idx n)
              (write-char #\space p)
              (write-fixnum ($bytevector-u8-ref x idx) p)
              (f (fxadd1 idx) n x p)))))
      (write-char #\) p)
      i))

  (define write-struct
    (lambda (x p m h i)
      (cond
        [(let ([rtd (struct-type-descriptor x)])
           (and (record-type-descriptor? rtd) 
                (record-type-opaque? rtd)))
         (write-char* "#<unknown>" p)
         i]
        [else
         (write-char #\# p)
         (write-char #\[ p)
         (let ([i (writer (struct-name x) p m h i)])
           (let ([n (struct-length x)])
             (let f ([idx 0] [i i])
               (cond
                 [(fx= idx n)
                  (write-char #\] p)
                  i]
                 [else 
                  (write-char #\space p)
                  (f (fxadd1 idx) 
                     (writer (struct-ref x idx) p m h
                             i))]))))])))
  
  (define initial?
    (lambda (c)
      (or (letter? c) (special-initial? c))))
  
  (define letter?
    (lambda (c)
      (or (and ($char<= #\a c) ($char<= c #\z))
          (and ($char<= #\A c) ($char<= c #\Z)))))
  
  (define digit?
    (lambda (c)
      (and ($char<= #\0 c) ($char<= c #\9))))
  
  (define special-initial? 
    (lambda (x)
      (memq x '(#\! #\$ #\% #\& #\* #\/ #\: #\< #\= #\> #\? #\^ #\_ #\~))))
  
  (define subsequent?
    (lambda (x)
      (or (initial? x)
          (digit? x)
          (special-subsequent? x))))
  
  (define special-subsequent?
    (lambda (x) 
      (memq x '(#\+ #\- #\. #\@))))
  
  (define subsequent*?
    (lambda (str i n)
      (or ($fx= i n)
          (and (subsequent? ($string-ref str i))
               (subsequent*? str ($fxadd1 i) n)))))
  
  (define peculiar-symbol-string?
    (lambda (str)
      (let ([n (string-length str)])
        (cond
          [(fx= n 1) 
           (memq (string-ref str 0) '(#\+ #\-))]
          [(fx>= n 2)
           (or (and (char=? (string-ref str 0) #\-)
                    (char=? (string-ref str 1) #\>)
                    (subsequent*? str 2 n))
               (string=? str "..."))]
          [else #f]))))

  (define valid-symbol-string?
    (lambda (str)
      (define normal-symbol-string?
        (lambda (str)
          (let ([n ($string-length str)])
            (and ($fx>= n 1)
                 (initial? ($string-ref str 0))
                 (subsequent*? str 1 n)))))
      (or (normal-symbol-string? str)
          (peculiar-symbol-string? str))))
  
  (define write-symbol-bar-esc-loop
    (lambda (x i n p)
      (unless ($fx= i n)
        (let* ([c ($string-ref x i)]
               [b ($char->fixnum c)])
          (cond
            [($fx< b 32)
             (cond
               [($fx< b 7) 
                (write-inline-hex b p)]
               [($fx< b 14)
                (write-char #\\ p)
                (write-char (string-ref "abtnvfr" ($fx- b 7)) p)]
               [else 
                (write-inline-hex b p)])]
            [(memq c '(#\\ #\|))
             (write-char #\\ p)
             (write-char c p)]
            [($fx< b 127) 
             (write-char c p)]
            [else 
             (write-inline-hex b p)]))
        (write-symbol-bar-esc-loop x ($fxadd1 i) n p))))
  
  (define write-symbol-bar-esc
    (lambda (x p)
      (write-char #\| p)
      (write-symbol-bar-esc-loop x 0 ($string-length x) p)
      (write-char #\| p)))
  
  (define-syntax ascii-map
    (lambda (x)
      ;;; r6rs prohibits bytevectors from being "datum"s
      ;;; oh well.
      (syntax-case x ()
        [(stx str) (string? (syntax->datum #'str))
         (let ([s (syntax->datum #'str)]
               [bv (make-bytevector 16 0)])
           (for-each
             (lambda (c)
               (let ([b (char->integer c)])
                 (let ([i (fxlogand b 7)]
                       [j (fxsra b 3)])
                   (bytevector-u8-set! bv j
                     (fxlogor (bytevector-u8-ref bv j)
                       (fxsll 1 i))))))
             (string->list s))
           (with-syntax ([bv (datum->syntax #'stx bv)])
             #'(quote bv)))])))

  (define subsequents-map 
   (ascii-map
     "!$%&*/:<=>?^_~+-.@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"))
  (define initials-map
   (ascii-map
     "!$%&*/:<=>?^_~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"))


  (define (in-map? byte map)
    (let ([i ($fxlogand byte 7)]
          [j ($fxsra byte 3)])
      (and 
        (fx< j ($bytevector-length map))
        (let ([mask ($fxsll 1 i)])
           (not ($fxzero? 
                  ($fxlogand mask
                    ($bytevector-u8-ref map j))))))))

  (define initial-categories 
    '(Lu Ll Lt Lm Lo Mn Nl No Pd Pc Po Sc Sm Sk So Co))
  (define subsequent-categories
    '(Nd Mc Me))

  (define (write-subsequent* str i j p)
    (unless ($fx= i j) 
      (let* ([c ($string-ref str i)]
             [b ($char->fixnum c)])
        (cond
          [(in-map? b subsequents-map) 
           (write-char c p)]
          [($fx< b 128) 
           (write-inline-hex b p)]
          [(and (print-unicode) 
             (let ([cat (char-general-category c)])
               (or (memq cat initial-categories)
                   (memq cat subsequent-categories))))
           (write-char c p)]
          [else 
           (write-inline-hex b p)]))
      (write-subsequent* str ($fxadd1 i) j p)))

  (define write-symbol-hex-esc
    (lambda (str p)
      (let ([n ($string-length str)])
        (cond
          [($fx= n 0) 
           (write-char #\| p) 
           (write-char #\| p)]
          [else
           (let* ([c0 ($string-ref str 0)]
                  [b0 ($char->fixnum c0)])
             (cond
               [(in-map? b0 initials-map) 
                (write-char c0 p)]
               [($fx< b0 128) (write-inline-hex b0 p)]
               [(and (print-unicode) 
                  (memq (char-general-category c0) initial-categories))
                (write-char c0 p)]
               [else (write-inline-hex b0 p)])
             (write-subsequent* str 1 n p))]))))


  (define (write-peculiar str p)
    (let ([n ($string-length str)])
      (cond
        [($fx= n 1) 
         (write-char ($string-ref str 0) p)]
        [(and ($fx>= n 2) 
              ($char= ($string-ref str 0) #\-)
              ($char= ($string-ref str 1) #\>))
         (write-char #\- p)
         (write-char #\> p)
         (write-subsequent* str 2 n p)]
        [(string=? str "...")
         (write-char #\. p)
         (write-char #\. p)
         (write-char #\. p)]
        [else (die 'write-peculiear "BUG")])))

  (define write-symbol
    (lambda (x p m)
      (write-symbol-string (symbol->string x) p m)))

  (define write-symbol-string
    (lambda (str p m)
      (if m
          (if (peculiar-symbol-string? str)
              (write-peculiar str p)
              (write-symbol-hex-esc str p))
          (write-char* str p))))
  
  (define write-gensym
    (lambda (x p m h i)
      (cond
        [(and m (print-gensym)) =>
         (lambda (gensym-how)
           (case gensym-how
             [(pretty)
              (let ([str (symbol->string x)])
                (write-char #\# p)
                (write-char #\: p)
                (write-symbol-string str p m))]
             [else
              (let ([str (symbol->string x)]
                    [ustr (gensym->unique-string x)])
                (write-char #\# p)
                (write-char #\{ p)
                (write-symbol-string str p m)
                (write-char #\space p)
                (write-symbol-bar-esc ustr p)
                (write-char #\} p))])
           i)]
        [else 
         (write-symbol x p m)
         i])))
  
  (define write-inline-hex
    (lambda (b p)
      (write-char #\\ p)
      (write-char #\x p)
      (if ($fxzero? b) 
          (write-char #\0 p)
          (write-positive-hex-fx b p))
      (write-char #\; p)))

  (define (write-hex x n p)
    (define s "0123456789ABCDEF")
    (unless (zero? n)
      (write-hex (sra x 4) (- n 1) p)
      (write-char (string-ref s (bitwise-and x #xF)) p)))

  (define write-string-escape
    (lambda (x p)
      (define loop 
        (lambda (x i n p)
         (unless (fx= i n)
           (let* ([c (string-ref x i)]
                  [b ($char->fixnum c)])
             (cond
               [($fx< b 32) 
                (cond
                  [($fx< b 7) 
                   (write-inline-hex b p)]
                  [($fx< b 14)
                   (write-char #\\ p)
                   (write-char (string-ref "abtnvfr" ($fx- b 7)) p)]
                  [else 
                   (write-inline-hex b p)])]
               [(or ($char= #\" c) ($char= #\\ c))
                (write-char #\\ p)
                (write-char c p)]
               [($fx< b 127)
                (write-char c p)]
               [(print-unicode) 
                (write-char c p)]
               [else
                (write-inline-hex b p)]))
           (loop x (fxadd1 i) n p)))) 
      (write-char #\" p)
      (loop x 0 (string-length x) p)
      (write-char #\" p)))
  
  (define write-string
    (lambda (x p m)
      (if m
          (write-string-escape x p)
          (write-char* x p))))
  
  (define write-fixnum
    (lambda (x p)
      (define loop
        (lambda (x p)
          (unless (fxzero? x)
            (loop (fxquotient x 10) p)
            (write-char 
               ($fixnum->char
                  ($fx+ (fxremainder x 10)
                        ($char->fixnum #\0)))
               p))))
      (cond
       [(fxzero? x) (write-char #\0 p)]
       [(fx< x 0)
        (write-char* (fixnum->string x) p)]
        ;(write-char #\- p)
        ;(if (fx= x -536870912)
        ;    (write-char* "536870912" p)
        ;    (loop (fx- 0 x) p))]
       [else (loop x p)])))
  
  (define write-char*
    (lambda (x p)
      (define loop 
        (lambda (x i n p)
         (unless (fx= i n)
           (write-char (string-ref x i) p)
           (loop x (fxadd1 i) n p)))) 
      (loop x 0 (string-length x) p)))
  
  (define macro
    (lambda (x h)
      (and 
        (pair? x)
        (let ([a ($car x)])
          (and
            (symbol? a)
            (let ([d ($cdr x)])
              (and (pair? d)
                (null? ($cdr d))
                (not (hashtable-ref h x #f))))
            (let ([p (get-fmt a)])
              (and (pair? p)
                (eq? (car p) 'read-macro)
                (string? (cdr p))
                p)))))))
  
  (define write-pair
    (lambda (x p m h i)
      (cond
        [(macro x h) =>
         (lambda (a) 
           (display (cdr a) p) 
           (writer (cadr x) p m h i))]
        [else
         (write-char #\( p)
         (let ([i (writer (car x) p m h i)])
           (let ([i (write-list (cdr x) p m h i)])
             (write-char #\) p)
             i))])))
  
  (define write-ref
    (lambda (n p)
      (write-char #\# p)
      (write-fixnum (fx- -1 n) p)
      (write-char #\# p)))
  
  (define write-mark
    (lambda (n p)
      (write-char #\# p)
      (write-fixnum (fx- -1 n) p)
      (write-char #\= p)))
  
  (define write-shareable
    (lambda (x p m h i k)
      (cond
        [(hashtable-ref h x #f) =>
         (lambda (n)
           (cond
             [(fx< n 0) 
              (write-ref n p)
              i]
             [(fx= n 0)
              (k x p m h i)]
             [else
              (let ([i (fx- i 1)])
                (hashtable-set! h x i)
                (write-mark i p)
                (k x p m h i))]))]
        [else (k x p m h i)])))
  
  (define writer
    (lambda (x p m h i)
      (cond
        [(pair? x) 
         (write-shareable x p m h i write-pair)]
        [(symbol? x)
         (if (gensym? x)
             (write-gensym x p m h i)
             (begin (write-symbol x p m) i))]
        [(fixnum? x)
         (write-fixnum x p)
         i]
        [(string? x)
         (write-string x p m)
         i]
        [(boolean? x)
         (write-char* (if x "#t" "#f") p)
         i]
        [(char? x)
         (write-character x p m)
         i]
        [(procedure? x)
         (cond
           [(let ([name (procedure-annotation x)])
              (and (symbol? name) name)) =>
            (lambda (name) 
              (write-char* "#<procedure " p) 
              (write-char* (symbol->string name) p)
              (write-char* ">" p))]
           [else (write-char* "#<procedure>" p)])
         i]
        [(output-port? x)
         (write-char* "#<output-port " p)
         (write-char* (if (binary-port? x) "(binary) " "(textual) ") p)
         (let ([i (writer (port-id x) p #t h i)])
           (write-char #\> p)
           i)]
        [(input-port? x)
         (write-char* "#<input-port " p)
         (write-char* (if (binary-port? x) "(binary) " "(textual) ") p)
         (let ([i (writer (port-id x) p #t h i)])
           (write-char #\> p)
           i)]
        [(vector? x)
         (write-shareable x p m h i write-vector)]
        [(bytevector? x)
         (write-shareable x p m h i write-bytevector)]
        [(null? x) 
         (write-char #\( p)
         (write-char #\) p)
         i]
        [(eq? x (void)) 
         (write-char* "#<void>" p)
         i]
        [(eof-object? x)
         (write-char* "#!eof" p)
         i]
        [(bwp-object? x)
         (write-char* "#!bwp" p)
         i]
        [(hashtable? x)
         (write-char* "#<hashtable>" p)
         i]
        ;[(record? x) 
        ; (write-shareable x p m h i write-struct)]
        [(struct? x)
         (let ([printer (struct-printer x)])
           (if (procedure? printer)
               (begin (printer x p) i)
               (write-shareable x p m h i write-struct)))]
        [(code? x)
         (write-char* "#<code>" p)]
        [($unbound-object? x)
         (write-char* "#<unbound-object>" p)
         i]
       ;;; [($forward-ptr? x) FIXME reinstate
       ;;;  (write-char* "#<forward-ptr>" p)
       ;;;  i]
        [(number? x)
         (write-char* (number->string x) p)
         i]
        [(transcoder? x)
         (write-char* "#<transcoder " p)
         (let ([n ($transcoder->data x)])
           (write-char* (number->string n) p))
         (write-char* ">" p)]
        [(pointer? x) 
         (write-char* "#<pointer #x" p)
         (write-hex 
           (pointer->integer x)
           (if (<= (fixnum-width) 32) 8 16)
           p)
         (write-char* ">" p)]
        [else 
         (write-char* "#<unknown>" p)
         i])))
  
  (define print-graph (make-parameter #f))
  
  (define (hasher x h)
    (define (vec-graph x i j h)
      (unless (fx= i j)
        (graph (vector-ref x i) h)
        (vec-graph x (fxadd1 i) j h)))
    (define (vec-dynamic x i j h)
      (unless (fx= i j)
        (dynamic (vector-ref x i) h)
        (vec-dynamic x (fxadd1 i) j h))) 
    (define (graph x h)
      (cond
        [(pair? x)
         (cond
           [(hashtable-ref h x #f) =>
            (lambda (n)
              (hashtable-set! h x (fxadd1 n)))]
           [else
            (hashtable-set! h x 0)
            (graph (car x) h)
            (graph (cdr x) h)])]
        [(vector? x)
         (cond
           [(hashtable-ref h x #f) =>
            (lambda (n)
              (hashtable-set! h x (fxadd1 n)))]
           [else
            (hashtable-set! h x 0)
            (vec-graph x 0 (vector-length x) h)])]
        [(gensym? x)
         (cond
           [(hashtable-ref h x #f) =>
            (lambda (n)
              (hashtable-set! h x (fxadd1 n)))])]
        [(struct? x) 
         (cond
           [(hashtable-ref h x #f) =>
            (lambda (n) 
              (hashtable-set! h x (fxadd1 n)))]
           [else
            (hashtable-set! h x 0)
            (let ([rtd (struct-type-descriptor x)])
              (unless 
                (and (record-type-descriptor? rtd) 
                     (record-type-opaque? rtd))
                (graph (struct-name x) h)
                (let ([n (struct-length x)])
                  (let f ([idx 0])
                    (unless (fx= idx n)
                      (graph (struct-ref x idx) h)
                      (f (fxadd1 idx)))))))])]
        ))
    (define (dynamic x h)
      (cond
        [(pair? x)
         (cond
           [(hashtable-ref h x #f) =>
            (lambda (n)
              (hashtable-set! h x (fxadd1 n)))]
           [else
            (hashtable-set! h x 0)
            (dynamic (car x) h)
            (dynamic (cdr x) h)
            (when (and (hashtable-ref h x #f)
                       (fxzero? (hashtable-ref h x #f)))
              (hashtable-set! h x #f))])]
        [(vector? x)
         (cond
           [(hashtable-ref h x #f) =>
            (lambda (n)
              (hashtable-set! h x (fxadd1 n)))]
           [else
            (hashtable-set! h x 0)
            (vec-dynamic x 0 (vector-length x) h)
            (when (and (hashtable-ref h x #f)
                       (fxzero? (hashtable-ref h x #f)))
              (hashtable-set! h x #f))])]
        [(struct? x) 
         (cond
           [(hashtable-ref h x #f) =>
            (lambda (n) 
              (hashtable-set! h x (fxadd1 n)))]
           [else
            (hashtable-set! h x 0)
            (let ([rtd (struct-type-descriptor x)])
              (unless 
                (and (record-type-descriptor? rtd) 
                     (record-type-opaque? rtd))
                (dynamic (struct-name x) h)
                (let ([n (struct-length x)])
                  (let f ([idx 0])
                    (unless (fx= idx n)
                      (dynamic (struct-ref x idx) h)
                      (f (fxadd1 idx)))))))
            (when (and (hashtable-ref h x #f)
                       (fxzero? (hashtable-ref h x #f)))
              (hashtable-set! h x #f))])]
        ;;; FIXME: recursive records/structs
        ))
    (if (print-graph) 
        (graph x h)
        (dynamic x h)))
  
  (define (write-to-port x p)
    (let ([h (make-eq-hashtable)])
      (hasher x h)
      (writer x p #t h 0))
    (flush-output-port p))
  
  (define (display-to-port x p)
    (let ([h (make-eq-hashtable)])
      (hasher x h)
      (writer x p #f h 0))
    (flush-output-port p))
  
  (define formatter
    (lambda (who p fmt args)
      ;;; first check
      (let f ([i 0] [args args])
        (cond
          [(fx= i (string-length fmt))
           (unless (null? args) 
             (die who 
               (format 
                 "extra arguments given for format string \x2036;~a\x2033;"
                 fmt)))]
          [else
           (let ([c (string-ref fmt i)])
             (cond
               [(eqv? c #\~)
                (let ([i (fxadd1 i)])
                  (when (fx= i (string-length fmt))
                    (die who "invalid ~ at end of format string" fmt))
                  (let ([c (string-ref fmt i)])
                   (cond
                     [(memv c '(#\~ #\%)) (f (fxadd1 i) args)]
                     [(memv c '(#\a #\s))
                      (when (null? args)
                        (die who "insufficient arguments"))
                      (f (fxadd1 i) (cdr args))]
                     [(memv c '(#\b #\o #\x #\d))
                      (when (null? args)
                        (die who "insufficient arguments"))
                      (let ([a (car args)])
                        (cond
                          [(or (fixnum? a) (bignum? a) (ratnum?  a)) 
                           (void)]
                          [(flonum? a)
                           (unless (eqv? c #\d) 
                             (die who 
                               (format "flonums cannot be printed with ~~~a" c)))]
                          [else 
                           (die who "not a number" a)]))
                      (f (fxadd1 i) (cdr args))]
                     [else
                      (die who "invalid sequence character after ~" c)])))]
               [else (f (fxadd1 i) args)]))]))
      ;;; then format
      (let f ([i 0] [args args])
        (unless (fx= i (string-length fmt))
          (let ([c (string-ref fmt i)])
            (cond
              [(eqv? c #\~)
               (let ([i (fxadd1 i)])
                 (let ([c (string-ref fmt i)])
                  (cond
                    [(eqv? c #\~) 
                     (write-char #\~ p)
                     (f (fxadd1 i) args)]
                    [(eqv? c #\%) 
                     (write-char #\newline p)
                     (f (fxadd1 i) args)] 
                    [(eqv? c #\a)
                     (display-to-port (car args) p)
                     (f (fxadd1 i) (cdr args))]
                    [(eqv? c #\s)
                     (write-to-port (car args) p)
                     (f (fxadd1 i) (cdr args))]
                    [(assv c '([#\b . 2] [#\o . 8] [#\x . 16] [#\d . 10]))
                     =>
                     (lambda (x)
                       (let ([a (car args)])
                         (cond
                           [(or (fixnum? a) (bignum? a) (ratnum? a))
                            (display-to-port (number->string a (cdr x)) p)]
                           [(flonum? a)
                            (display-to-port (number->string a) p)]
                           [else 
                            (die who "BUG: not a number" a)]))
                       (f (fxadd1 i) (cdr args)))]
                    [else (die who "BUG" c)])))]
              [else 
               (write-char c p)
               (f (fxadd1 i) args)]))))
      ;;; then flush
      (flush-output-port p)))
  
  (define fprintf
    (lambda (p fmt . args)
      (assert-open-textual-output-port p 'fprintf)
      (unless (string? fmt)
        (die 'fprintf "not a string" fmt))
      (formatter 'fprintf p fmt args)))

  (define display-error
    (lambda (errname who fmt args)
      (unless (string? fmt)
        (die 'print-error "not a string" fmt))
      (let ([p (standard-error-port)])
        (if who
            (fprintf p "~a in ~a: " errname who)
            (fprintf p "~a: " errname))
        (formatter 'print-error p fmt args)
        (write-char #\. p)
        (newline p))))
  
  (define format
    (lambda (fmt . args)
      (unless (string? fmt)
        (die 'format "not a string" fmt))
      (let-values ([(p e) (open-string-output-port)])
        (formatter 'format p fmt args)
        (e))))
   
  (define printf 
    (lambda (fmt . args)
      (unless (string? fmt)
        (die 'printf "not a string" fmt))
      (formatter 'printf (current-output-port) fmt args)))
  
  (define write 
    (case-lambda
      [(x) (write-to-port x (current-output-port))]
      [(x p)
       (assert-open-textual-output-port p 'write)
       (write-to-port x p)]))

  (define (put-datum p x)
    (assert-open-textual-output-port p 'put-datum)
    (write-to-port x p))

  (define display 
    (case-lambda
      [(x) (display-to-port x (current-output-port))]
      [(x p)
       (assert-open-textual-output-port p 'display)
       (display-to-port x p)]))

  (define print-error 
    (lambda (who fmt . args)
      (display-error "Error" who fmt args)))

  (define warning 
    (lambda (who fmt . args)
      (display-error "Warning" who fmt args)))

  (define (assert-open-textual-output-port p who)
    (unless (output-port? p) 
      (die who "not an output port" p))
    (unless (textual-port? p) 
      (die who "not a textual port" p))
    (when (port-closed? p) 
      (die who "port is closed" p)))

  )