[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

customizing write-char (was Re: Format strings are wrong)

This page is part of the web mail archives of SRFI 48 from before July 7th, 2015. The new archives for SRFI 48 contain all messages, not just those from before July 7th, 2015.



Taylor's idea of passing a custom write-char is not so useful by
itself, but if you turn it into a FOLD operation then you can get more
interesting results.  Below is an implementation of fundamental
write/display enumerators and some examples of customizing them.

Although interesting, I don't think this could taken seriously due to
being extremely slow.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; general string utilities

(define (display->string x)
  (with-output-to-string (lambda () (display x))))

(define (write->string x)
  (with-output-to-string (lambda () (write x))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; kind of a fold for folders, or a partial application compose

;; (procN kons ... (proc2 kons (proc1 kons knil)))
(define (cat>> kons knil procs)
  (let loop ((ls procs) (acc knil))
    (if (null? ls) acc (loop (cdr ls) ((car ls) kons acc)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; basic formatters

(define (display>> obj)
  (lambda (kons knil)
    (cond
      ((string? obj)
       (string-fold kons knil obj))
      ((pair? obj)
       (kons
        #\)
        (let loop ((acc (kons #\( knil))
                   (ls obj))
          (let ((acc2 ((display>> (car ls)) kons acc))
                (rest (cdr ls)))
            (cond
              ((null? rest) acc2)
              ((pair? rest) (loop (kons #\space acc2) rest))
              (else
               ((display>> rest) kons ((display>> kons) acc2 " . "))))))))
      ((vector? obj)
       ((display>> (vector->list obj)) kons (kons #\# knil)))
      (else ;; add more cases to avoid this
       ((display>> (display->string obj)) kons knil)))))

(define (write>> obj)
  (lambda (kons knil)
    (cond
      ((string? obj)
       (kons #\" (string-fold kons (kons #\" knil) obj)))
      ((pair? obj)
       (kons
        #\)
        (let loop ((acc (kons #\( knil))
                   (ls obj))
          (let ((acc2 ((write>> (car ls)) kons acc))
                (rest (cdr ls)))
            (cond
              ((null? rest) acc2)
              ((pair? rest) (loop (kons #\space acc2) rest))
              (else
               ((write>> rest) kons ((write>> " . ") kons acc2))))))))
      ((vector? obj)
       ((write>> (vector->list obj)) kons (kons #\# knil)))
      (else ;; note, intentionally display>>
       ((display>> (write->string obj)) kons knil)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; example 1: formatting with a column counter

(define (column-writer c i)
  (write-char c)
  (if (eqv? c #\newline) 0 (+ i 1)))

(define (fmt>> . procs)
  (cat>> column-writer 0 procs))

;; a "constant" procedure
(define (fresh-line kons knil)
  (if (zero? knil)
    knil
    (kons #\newline knil)))

(define (tab>> col . opt)
  (lambda (kons knil)
    (let* ((modulo? (and (pair? opt) (car opt)))
           (width (if modulo? (modulo col knil)
                      (- col knil))))
      (if (positive? width)
        ((display>> (make-string width #\space)) kons knil)
        knil))))

; (fmt>> (display>> "value: ") (write>> '(a "b" 3)) fresh-line)

; (fmt>> (display>> "Name: ") (write>> "Socrates") (tab>> 20)
;        (display>> "Sex: ") (write>> "male") fresh-line
;        (display>> "Location: ") (write>> "Athens") (tab>> 20)
;        (display>> "Job: ") (write>> "corrupting the youth") fresh-line)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; example 2: case folding

(define (upcase>> . procs)
  (lambda (kons knil)
    (cat>> (lambda (c i) (kons (char-upcase c) i)) knil procs)))

(define (downcase>> . procs)
  (lambda (kons knil)
    (cat>> (lambda (c i) (kons (char-downcase c) i)) knil procs)))

;; titlecase requires storing previous char's script type in the
;; accumulator

; (fmt>> (upcase>> (display>> "hElLo ")) (downcase>> (display>> "WOrlD!")))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; example 3: default radix

;; This example changes the meaning of the accumulator from
;; current-column to current-radix.  Similarly you could specify default
;; precision for floating point numbers.  A full-featured set of
;; formatting procedures would probably define a record to hold various
;; state information and pass that as the accumulator.

;; current radix is stored in knil
(define (number>> n . opt)
  (lambda (kons knil)
    (let ((radix (if (pair? opt) (car opt) knil)))
      ((display>> (number->string n radix)) kons knil))))

;; just overrides current radix
(define (radix>> radix . procs)
  (lambda (kons knil)
    (cat>> kons radix procs)))

;; start off with default radix 10
(define (fmt-radix>> . procs)
  (cat>> (lambda (c i) (write-char c) i) 10 procs))

; (fmt-radix>> (display>> "decimal: ") (number>> 123)
;              (radix>> 16
;                       (display>> " hex: ") (number>> 123)
;                       (display>> " octal: ") (number>> 123 8)))

-- 
Alex