Title Formatting Author Joo ChurlSoo Abstract This SRFI introduces the FMT procedure that converts any object to a string. Unlike the procedure called FORMAT, this FMT procedure takes one object as the first argument and accepts several optional arguments. Rationale The FMT procedure provides a handy optional and functional interface. Specification (FMT [[] [] [] [] [] []]) (FMT [[] [] [] []]) * is any numeric expression. * are any expressions except number. * is an exact integer whose absolute value specifies the width of the resulting string. When the resulting string has fewer characters than the absolute value of , it is padded with s, either on the left if is positive, or on the right if is negative. On the other hand, when the resulting string has more characters than the absolute value of , the is ignored. * is a non-negative exact integer that specifies the number of decimal digits after decimal point. * is a non-negative exact integer that specifies the number of characters of the resulting string. * is a padding character. * is a symbol: b (binary), d (decimal), o (octal), x (hexadecimal) * is a procedure: display, write * If is a procedure + and is a positive number without a positive sign, the positive sign is prefixed to the . * is a symbol: e (exact), i (inexact) The order of optional arguments is ignored except that or can be defined only after is defined. Examples (fmt 129.995 10 2) " 130.00" (fmt 129.995 -10 2) "130.00 " (fmt 129.995 10 #\0 2) "0000130.00" (fmt 129.995 #\0 10 + 2) "+000130.00" (fmt 129.995 #\0 10 + 2 'o 'e) error (fmt 129.995 #\0 10 + 'o 'e) "+000000202" (fmt (sqrt -5) 10) "0.0+2.23606797749979i" (fmt (sqrt -5) 10 2) "0.00+2.24i" (fmt 3.14159e12 10 2 +) " +3.14e12" (fmt #x123 'o 10) " 443" (fmt #x123 -10 3 + #\*) "+291.000**" (fmt "string" 10) " string" (fmt "string" -10 write) "\"string\" " (fmt "string" 10 3) " str" (fmt "string" -10 3) "str " (fmt "string" #\- -10 3) "str-------" (fmt #\a write) "#\\a" (fmt #\a display) "a" (fmt #\a 10) " a" (fmt 'symbol 10) " symbol" (fmt '(1 #\a "str" sym '(a)) write) "(1 #\\a \"str\" sym (quote (a)))" (fmt '(1 #\a "str" sym '(a))) "(1 a str sym (quote (a)))" (fmt '(1 #\a "str" sym '(a)) 10) "(1 a str sym (quote (a)))" (fmt '(1 #\a "str" sym '(a)) 10 10) "(1 a str s" (fmt #(1 #\a "str" sym '(a)) 10) "#(1 a str sym (quote (a)))" (fmt #(1 #\a "str" sym '(a)) 10 write) "#(1 #\\a \"str\" sym (quote (a)))" Implementation The implementation below requires SRFI-6 (Basic string ports), SRFI-8 (Receive), SRFI-13 (String library), and SRFI-23 (Error reporting mechanism). (define (opt-values rest-list . default-list) (let loop ((rest-list rest-list) (default-list default-list) (result '())) (if (null? default-list) (if (null? rest-list) (apply values (reverse result)) (error "fmt: bad argument" rest-list `(null? ,rest-list))) (let ((default (car default-list))) (if (or (list? default) (and (pair? default) (procedure? (cdr default)))) (let lp ((rest rest-list) (head '())) (if (null? rest) (loop (reverse head) (cdr default-list) (cons (car default) result)) (if (list? default) (if (member (car rest) default) (loop (append (reverse head) (cdr rest)) (cdr default-list) (cons (car rest) result)) (lp (cdr rest) (cons (car rest) head))) (if ((cdr default) (car rest)) (loop (append (reverse head) (cdr rest)) (cdr default-list) (cons (car rest) result)) (lp (cdr rest) (cons (car rest) head)))))) (error "fmt: bad default" default)))))) (define (fmt expr . rest) (if (number? expr) (receive (width depth char radix plus exactness) (opt-values rest (cons #f number?) (cons #f number?) (cons #f char?) (list 'd 'b 'o 'x) (cons #f (lambda (x) (eq? x +))) (cons #f (lambda (x) (memq x '(e i))))) (and (memq radix '(b o x)) (or depth (and (inexact? expr) (not (eq? exactness 'e))) (eq? exactness 'i)) (error "fmt: non-decimal cannot be inexact" radix 'radix)) (and depth (eq? exactness 'e) (error "fmt: exact number cannot have a decimal point" depth 'depth)) (and char (not width) (error "fmt: unnecessary padding character" char 'char)) (let* ((width (or width 0)) (char (or char #\space)) (sign (if (< width 0) '- '+)) (str (number->string (if exactness (if (eq? exactness 'e) (if (inexact? expr) (inexact->exact expr) expr) (if (exact? expr) (exact->inexact expr) expr)) expr) (cdr (assq radix '((b . 2) (d . 10) (o . 8) (x . 16)))))) (str (if depth (let ((e-index (or (string-index str #\e) (string-index str #\E))) (+-index (string-index str #\+ 1))) (define (mold str dep) (let ((len (string-length str)) (index (string-index str #\.))) (if index (let ((d-len (- len index 1))) (if (<= d-len dep) (string-append str (make-string (- dep d-len) #\0)) (mold (number->string (+ (string->number (substring str 0 (+ (if (= dep 0) 0 1) index dep))) (if (< 4 (string->number (string (string-ref str (+ 1 index dep))))) (expt 0.1 dep) 0))) dep))) (string-append str "." (make-string dep #\0))))) (cond (e-index (string-append (mold (substring str 0 e-index) depth) (substring str e-index (string-length str)))) (+-index (string-append (mold (substring str 0 +-index) depth) "+" (mold (substring str (+ 1 +-index) (- (string-length str) 1)) depth) (string (string-ref str (- (string-length str) 1))))) (else (mold str depth)))) str)) (str (if (and (< 0 (real-part expr)) (not (eq? #\+ (string-ref str 0))) plus) (string-append "+" str) str)) (len (string-length str)) (pad (- (abs width) len))) (cond ((<= pad 0) str) ((eq? sign '+) (if (and (not (eqv? char #\space)) (or (eqv? #\+ (string-ref str 0)) (eqv? #\- (string-ref str 0)))) (string-append (string (string-ref str 0)) (make-string pad char) (substring str 1 len)) (string-append (make-string pad char) str))) (else (string-append str (make-string pad char)))))) (receive (width depth char show) (opt-values rest (cons #f number?) (cons #f number?) (cons #f char?) (list display write)) (and char (not width) (error "fmt: unnecessary padding character" char 'char)) (let* ((width (or width 0)) (char (or char #\space)) (sign (if (< width 0) '- '+)) (str (get-output-string (let ((str-port (open-output-string))) (show expr str-port) str-port))) (str (if (and depth (< depth (string-length str))) (substring str 0 depth) str)) (pad (- (abs width) (string-length str)))) (cond ((<= pad 0) str) ((eq? sign '+) (string-append (make-string pad char) str)) (else (string-append str (make-string pad char)))))))) Copyright Copyright (C) Joo ChurlSoo (2004). All Rights Reserved. This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works. However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Scheme Request For Implementation process or editors, except as needed for the purpose of developing SRFIs in which case the procedures for copyrights defined in the SRFI process must be followed, or as required to translate it into languages other than English. The limited permissions granted above are perpetual and will not be revoked by the authors or their successors or assigns. This document and the information contained herein is provided on an "AS IS" basis and THE AUTHOR AND THE SRFI EDITORS DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.