;;;; "format.scm" Common LISP text output formatter for SLIB ;;; Copyright (C) 2010, 2011, 2012, 2013 Free Software Foundation, Inc. ;;; ;;; This library is free software; you can redistribute it and/or ;;; modify it under the terms of the GNU Lesser General Public ;;; License as published by the Free Software Foundation; either ;;; version 3 of the License, or (at your option) any later version. ;;; ;;; This library 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 ;;; Lesser General Public License for more details. ;;; ;;; You should have received a copy of the GNU Lesser General Public ;;; License along with this library; if not, write to the Free Software ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ;;; ;;; This code was orignally in the public domain. ;;; ;;; Written 1992-1994 by Dirk Lutzebaeck (lutzeb@cs.tu-berlin.de). ;;; ;;; Authors of the version from SLIB (< 1.4) were Ken Dickey and Aubrey ;;; Jaffer. ;;; ;;; Assimilated into Guile May 1999. ;;; ;;; Please don't bother the original authors with bug reports, though; ;;; send them to bug-guile@gnu.org. ;;; (define-module (ice-9 format) #:autoload (ice-9 pretty-print) (pretty-print truncated-print) #:autoload (ice-9 i18n) (%global-locale number->locale-string) #:replace (format)) (define format:version "3.0") (define (format destination format-string . format-args) (if (not (string? format-string)) (error "format: expected a string for format string" format-string)) (let* ((port (cond ((not destination) (open-output-string)) ((boolean? destination) (current-output-port)) ; boolean but not false ((output-port? destination) destination) ((number? destination) (issue-deprecation-warning "Passing a number to format as the port is deprecated." "Pass (current-error-port) instead.") (current-error-port)) (else (error "format: bad destination `~a'" destination)))) (output-col (or (port-column port) 0)) (flush-output? #f)) (define format:case-conversion #f) (define format:pos 0) ; curr. format string parsing position (define format:arg-pos 0) ; curr. format argument position ; this is global for error presentation ;; format string and char output routines on port (define (format:out-str str) (if format:case-conversion (display (format:case-conversion str) port) (display str port)) (set! output-col (+ output-col (string-length str)))) (define (format:out-char ch) (if format:case-conversion (display (format:case-conversion (string ch)) port) (write-char ch port)) (set! output-col (if (char=? ch #\newline) 0 (+ output-col 1)))) ;;(define (format:out-substr str i n) ; this allocates a new string ;; (display (substring str i n) port) ;; (set! output-col (+ output-col n))) (define (format:out-substr str i n) (do ((k i (+ k 1))) ((= k n)) (write-char (string-ref str k) port)) (set! output-col (+ output-col (- n i)))) ;;(define (format:out-fill n ch) ; this allocates a new string ;; (format:out-str (make-string n ch))) (define (format:out-fill n ch) (do ((i 0 (+ i 1))) ((= i n)) (write-char ch port)) (set! output-col (+ output-col n))) ;; format's user error handler (define (format:error . args) ; never returns! (let ((port (current-error-port))) (set! format:error format:intern-error) (if (not (zero? format:arg-pos)) (set! format:arg-pos (- format:arg-pos 1))) (format port "~%FORMAT: error with call: (format ~a \"~a<===~a\" ~ ~{~a ~}===>~{~a ~})~% " destination (substring format-string 0 format:pos) (substring format-string format:pos (string-length format-string)) (list-head format-args format:arg-pos) (list-tail format-args format:arg-pos)) (apply format port args) (newline port) (set! format:error format:error-save) (format:abort))) (define (format:intern-error . args) ;;if something goes wrong in format:error (display "FORMAT: INTERNAL ERROR IN FORMAT:ERROR!") (newline) (display " destination: ") (write destination) (newline) (display " format string: ") (write format-string) (newline) (display " format args: ") (write format-args) (newline) (display " error args: ") (write args) (newline) (set! format:error format:error-save) (format:abort)) (define format:error-save format:error) (define format:parameter-characters '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\- #\+ #\v #\# #\')) (define (format:format-work format-string arglist) ; does the formatting work (letrec ((format-string-len (string-length format-string)) (arg-pos 0) ; argument position in arglist (arg-len (length arglist)) ; number of arguments (modifier #f) ; 'colon | 'at | 'colon-at | #f (params '()) ; directive parameter list (param-value-found #f) ; a directive ; parameter value ; found (conditional-nest 0) ; conditional nesting level (clause-pos 0) ; last cond. clause ; beginning char pos (clause-default #f) ; conditional default ; clause string (clauses '()) ; conditional clause ; string list (conditional-type #f) ; reflects the ; contional modifiers (conditional-arg #f) ; argument to apply the conditional (iteration-nest 0) ; iteration nesting level (iteration-pos 0) ; iteration string ; beginning char pos (iteration-type #f) ; reflects the ; iteration modifiers (max-iterations #f) ; maximum number of ; iterations (recursive-pos-save format:pos) (next-char ; gets the next char ; from format-string (lambda () (let ((ch (peek-next-char))) (set! format:pos (+ 1 format:pos)) ch))) (peek-next-char (lambda () (if (>= format:pos format-string-len) (format:error "illegal format string") (string-ref format-string format:pos)))) (one-positive-integer? (lambda (params) (cond ((null? params) #f) ((and (integer? (car params)) (>= (car params) 0) (= (length params) 1)) #t) (else (format:error "one positive integer parameter expected"))))) (next-arg (lambda () (if (>= arg-pos arg-len) (begin (set! format:arg-pos (+ arg-len 1)) (format:error "missing argument(s)"))) (add-arg-pos 1) (list-ref arglist (- arg-pos 1)))) (prev-arg (lambda () (add-arg-pos -1) (if (negative? arg-pos) (format:error "missing backward argument(s)")) (list-ref arglist arg-pos))) (rest-args (lambda () (let loop ((l arglist) (k arg-pos)) ; list-tail definition (if (= k 0) l (loop (cdr l) (- k 1)))))) (add-arg-pos (lambda (n) (set! arg-pos (+ n arg-pos)) (set! format:arg-pos arg-pos))) (anychar-dispatch ; dispatches the format-string (lambda () (if (>= format:pos format-string-len) arg-pos ; used for ~? continuance (let ((char (next-char))) (cond ((char=? char #\~) (set! modifier #f) (set! params '()) (set! param-value-found #f) (tilde-dispatch)) (else (if (and (zero? conditional-nest) (zero? iteration-nest)) (format:out-char char)) (anychar-dispatch))))))) (tilde-dispatch (lambda () (cond ((>= format:pos format-string-len) (format:out-str "~") ; tilde at end of ; string is just ; output arg-pos) ; used for ~? ; continuance ((and (or (zero? conditional-nest) (memv (peek-next-char) ; find conditional ; directives (append '(#\[ #\] #\; #\: #\@ #\^) format:parameter-characters))) (or (zero? iteration-nest) (memv (peek-next-char) ; find iteration ; directives (append '(#\{ #\} #\: #\@ #\^) format:parameter-characters)))) (case (char-upcase (next-char)) ;; format directives ((#\A) ; Any -- for humans (set! format:read-proof (memq modifier '(colon colon-at))) (format:out-obj-padded (memq modifier '(at colon-at)) (next-arg) #f params) (anychar-dispatch)) ((#\S) ; Slashified -- for parsers (set! format:read-proof (memq modifier '(colon colon-at))) (format:out-obj-padded (memq modifier '(at colon-at)) (next-arg) #t params) (anychar-dispatch)) ((#\D) ; Decimal (format:out-num-padded modifier (next-arg) params 10) (anychar-dispatch)) ((#\H) ; Localized number (let* ((num (next-arg)) (locale (case modifier ((colon) (next-arg)) (else %global-locale))) (argc (length params)) (width (format:par params argc 0 #f "width")) (decimals (format:par params argc 1 #t "decimals")) (padchar (integer->char (format:par params argc 2 format:space-ch "padchar"))) (str (number->locale-string num decimals locale))) (format:out-str (if (and width (< (string-length str) width)) (string-pad str width padchar) str))) (anychar-dispatch)) ((#\X) ; Hexadecimal (format:out-num-padded modifier (next-arg) params 16) (anychar-dispatch)) ((#\O) ; Octal (format:out-num-padded modifier (next-arg) params 8) (anychar-dispatch)) ((#\B) ; Binary (format:out-num-padded modifier (next-arg) params 2) (anychar-dispatch)) ((#\R) (if (null? params) (format:out-obj-padded ; Roman, cardinal, ; ordinal numerals #f ((case modifier ((at) format:num->roman) ((colon-at) format:num->old-roman) ((colon) format:num->ordinal) (else format:num->cardinal)) (next-arg)) #f params) (format:out-num-padded ; any Radix modifier (next-arg) (cdr params) (car params))) (anychar-dispatch)) ((#\F) ; Fixed-format floating-point (format:out-fixed modifier (next-arg) params) (anychar-dispatch)) ((#\E) ; Exponential floating-point (format:out-expon modifier (next-arg) params) (anychar-dispatch)) ((#\G) ; General floating-point (format:out-general modifier (next-arg) params) (anychar-dispatch)) ((#\$) ; Dollars floating-point (format:out-dollar modifier (next-arg) params) (anychar-dispatch)) ((#\I) ; Complex numbers (let ((z (next-arg))) (if (not (complex? z)) (format:error "argument not a complex number")) (format:out-fixed modifier (real-part z) params) (format:out-fixed 'at (imag-part z) params) (format:out-char #\i)) (anychar-dispatch)) ((#\C) ; Character (let ((ch (if (one-positive-integer? params) (integer->char (car params)) (next-arg)))) (if (not (char? ch)) (format:error "~~c expects a character")) (case modifier ((at) (format:out-str (object->string ch))) ((colon) (let ((c (char->integer ch))) (if (< c 0) (set! c (+ c 256))) ; compensate ; complement ; impl. (cond ((< c #x20) ; assumes that control ; chars are < #x20 (format:out-char #\^) (format:out-char (integer->char (+ c #x40)))) ((>= c #x7f) (format:out-str "#\\") (format:out-str (number->string c 8))) (else (format:out-char ch))))) (else (format:out-char ch)))) (anychar-dispatch)) ((#\P) ; Plural (if (memq modifier '(colon colon-at)) (prev-arg)) (let ((arg (next-arg))) (if (not (number? arg)) (format:error "~~p expects a number argument")) (if (= arg 1) (if (memq modifier '(at colon-at)) (format:out-char #\y)) (if (memq modifier '(at colon-at)) (format:out-str "ies") (format:out-char #\s)))) (anychar-dispatch)) ((#\~) ; Tilde (if (one-positive-integer? params) (format:out-fill (car params) #\~) (format:out-char #\~)) (anychar-dispatch)) ((#\%) ; Newline (if (one-positive-integer? params) (format:out-fill (car params) #\newline) (format:out-char #\newline)) (set! output-col 0) (anychar-dispatch)) ((#\&) ; Fresh line (if (one-positive-integer? params) (begin (if (> (car params) 0) (format:out-fill (- (car params) (if (> output-col 0) 0 1)) #\newline)) (set! output-col 0)) (if (> output-col 0) (format:out-char #\newline))) (anychar-dispatch)) ((#\_) ; Space character (if (one-positive-integer? params) (format:out-fill (car params) #\space) (format:out-char #\space)) (anychar-dispatch)) ((#\/) ; Tabulator character (if (one-positive-integer? params) (format:out-fill (car params) #\tab) (format:out-char #\tab)) (anychar-dispatch)) ((#\|) ; Page seperator (if (one-positive-integer? params) (format:out-fill (car params) #\page) (format:out-char #\page)) (set! output-col 0) (anychar-dispatch)) ((#\T) ; Tabulate (format:tabulate modifier params) (anychar-dispatch)) ((#\Y) ; Structured print (let ((width (if (one-positive-integer? params) (car params) 79))) (case modifier ((at) (format:out-str (call-with-output-string (lambda (p) (truncated-print (next-arg) p #:width width))))) ((colon-at) (format:out-str (call-with-output-string (lambda (p) (truncated-print (next-arg) p #:width (max (- width output-col) 1)))))) ((colon) (format:error "illegal modifier in ~~?")) (else (pretty-print (next-arg) port #:width width) (set! output-col 0)))) (anychar-dispatch)) ((#\? #\K) ; Indirection (is "~K" in T-Scheme) (cond ((memq modifier '(colon colon-at)) (format:error "illegal modifier in ~~?")) ((eq? modifier 'at) (let* ((frmt (next-arg)) (args (rest-args))) (add-arg-pos (format:format-work frmt args)))) (else (let* ((frmt (next-arg)) (args (next-arg))) (format:format-work frmt args)))) (anychar-dispatch)) ((#\!) ; Flush output (set! flush-output? #t) (anychar-dispatch)) ((#\newline) ; Continuation lines (if (eq? modifier 'at) (format:out-char #\newline)) (if (< format:pos format-string-len) (do ((ch (peek-next-char) (peek-next-char))) ((or (not (char-whitespace? ch)) (= format:pos (- format-string-len 1)))) (if (eq? modifier 'colon) (format:out-char (next-char)) (next-char)))) (anychar-dispatch)) ((#\*) ; Argument jumping (case modifier ((colon) ; jump backwards (if (one-positive-integer? params) (do ((i 0 (+ i 1))) ((= i (car params))) (prev-arg)) (prev-arg))) ((at) ; jump absolute (set! arg-pos (if (one-positive-integer? params) (car params) 0))) ((colon-at) (format:error "illegal modifier `:@' in ~~* directive")) (else ; jump forward (if (one-positive-integer? params) (do ((i 0 (+ i 1))) ((= i (car params))) (next-arg)) (next-arg)))) (anychar-dispatch)) ((#\() ; Case conversion begin (set! format:case-conversion (case modifier ((at) string-capitalize-first) ((colon) string-capitalize) ((colon-at) string-upcase) (else string-downcase))) (anychar-dispatch)) ((#\)) ; Case conversion end (if (not format:case-conversion) (format:error "missing ~~(")) (set! format:case-conversion #f) (anychar-dispatch)) ((#\[) ; Conditional begin (set! conditional-nest (+ conditional-nest 1)) (cond ((= conditional-nest 1) (set! clause-pos format:pos) (set! clause-default #f) (set! clauses '()) (set! conditional-type (case modifier ((at) 'if-then) ((colon) 'if-else-then) ((colon-at) (format:error "illegal modifier in ~~[")) (else 'num-case))) (set! conditional-arg (if (one-positive-integer? params) (car params) (next-arg))))) (anychar-dispatch)) ((#\;) ; Conditional separator (if (zero? conditional-nest) (format:error "~~; not in ~~[~~] conditional")) (if (not (null? params)) (format:error "no parameter allowed in ~~;")) (if (= conditional-nest 1) (let ((clause-str (cond ((eq? modifier 'colon) (set! clause-default #t) (substring format-string clause-pos (- format:pos 3))) ((memq modifier '(at colon-at)) (format:error "illegal modifier in ~~;")) (else (substring format-string clause-pos (- format:pos 2)))))) (set! clauses (append clauses (list clause-str))) (set! clause-pos format:pos))) (anychar-dispatch)) ((#\]) ; Conditional end (if (zero? conditional-nest) (format:error "missing ~~[")) (set! conditional-nest (- conditional-nest 1)) (if modifier (format:error "no modifier allowed in ~~]")) (if (not (null? params)) (format:error "no parameter allowed in ~~]")) (cond ((zero? conditional-nest) (let ((clause-str (substring format-string clause-pos (- format:pos 2)))) (if clause-default (set! clause-default clause-str) (set! clauses (append clauses (list clause-str))))) (case conditional-type ((if-then) (if conditional-arg (format:format-work (car clauses) (list conditional-arg)))) ((if-else-then) (add-arg-pos (format:format-work (if conditional-arg (cadr clauses) (car clauses)) (rest-args)))) ((num-case) (if (or (not (integer? conditional-arg)) (< conditional-arg 0)) (format:error "argument not a positive integer")) (if (not (and (>= conditional-arg (length clauses)) (not clause-default))) (add-arg-pos (format:format-work (if (>= conditional-arg (length clauses)) clause-default (list-ref clauses conditional-arg)) (rest-args)))))))) (anychar-dispatch)) ((#\{) ; Iteration begin (set! iteration-nest (+ iteration-nest 1)) (cond ((= iteration-nest 1) (set! iteration-pos format:pos) (set! iteration-type (case modifier ((at) 'rest-args) ((colon) 'sublists) ((colon-at) 'rest-sublists) (else 'list))) (set! max-iterations (if (one-positive-integer? params) (car params) #f)))) (anychar-dispatch)) ((#\}) ; Iteration end (if (zero? iteration-nest) (format:error "missing ~~{")) (set! iteration-nest (- iteration-nest 1)) (case modifier ((colon) (if (not max-iterations) (set! max-iterations 1))) ((colon-at at) (format:error "illegal modifier"))) (if (not (null? params)) (format:error "no parameters allowed in ~~}")) (if (zero? iteration-nest) (let ((iteration-str (substring format-string iteration-pos (- format:pos (if modifier 3 2))))) (if (string=? iteration-str "") (set! iteration-str (next-arg))) (case iteration-type ((list) (let ((args (next-arg)) (args-len 0)) (if (not (list? args)) (format:error "expected a list argument")) (set! args-len (length args)) (do ((arg-pos 0 (+ arg-pos (format:format-work iteration-str (list-tail args arg-pos)))) (i 0 (+ i 1))) ((or (>= arg-pos args-len) (and max-iterations (>= i max-iterations))))))) ((sublists) (let ((args (next-arg)) (args-len 0)) (if (not (list? args)) (format:error "expected a list argument")) (set! args-len (length args)) (do ((arg-pos 0 (+ arg-pos 1))) ((or (>= arg-pos args-len) (and max-iterations (>= arg-pos max-iterations)))) (let ((sublist (list-ref args arg-pos))) (if (not (list? sublist)) (format:error "expected a list of lists argument")) (format:format-work iteration-str sublist))))) ((rest-args) (let* ((args (rest-args)) (args-len (length args)) (usedup-args (do ((arg-pos 0 (+ arg-pos (format:format-work iteration-str (list-tail args arg-pos)))) (i 0 (+ i 1))) ((or (>= arg-pos args-len) (and max-iterations (>= i max-iterations))) arg-pos)))) (add-arg-pos usedup-args))) ((rest-sublists) (let* ((args (rest-args)) (args-len (length args)) (usedup-args (do ((arg-pos 0 (+ arg-pos 1))) ((or (>= arg-pos args-len) (and max-iterations (>= arg-pos max-iterations))) arg-pos) (let ((sublist (list-ref args arg-pos))) (if (not (list? sublist)) (format:error "expected list arguments")) (format:format-work iteration-str sublist))))) (add-arg-pos usedup-args))) (else (format:error "internal error in ~~}"))))) (anychar-dispatch)) ((#\^) ; Up and out (let* ((continue (cond ((not (null? params)) (not (case (length params) ((1) (zero? (car params))) ((2) (= (list-ref params 0) (list-ref params 1))) ((3) (<= (list-ref params 0) (list-ref params 1) (list-ref params 2))) (else (format:error "too much parameters"))))) (format:case-conversion ; if conversion stop conversion (set! format:case-conversion string-copy) #t) ((= iteration-nest 1) #t) ((= conditional-nest 1) #t) ((>= arg-pos arg-len) (set! format:pos format-string-len) #f) (else #t)))) (if continue (anychar-dispatch)))) ;; format directive modifiers and parameters ((#\@) ; `@' modifier (if (memq modifier '(at colon-at)) (format:error "double `@' modifier")) (set! modifier (if (eq? modifier 'colon) 'colon-at 'at)) (tilde-dispatch)) ((#\:) ; `:' modifier (if (memq modifier '(colon colon-at)) (format:error "double `:' modifier")) (set! modifier (if (eq? modifier 'at) 'colon-at 'colon)) (tilde-dispatch)) ((#\') ; Character parameter (if modifier (format:error "misplaced modifier")) (set! params (append params (list (char->integer (next-char))))) (set! param-value-found #t) (tilde-dispatch)) ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\- #\+) ; num. paramtr (if modifier (format:error "misplaced modifier")) (let ((num-str-beg (- format:pos 1)) (num-str-end format:pos)) (do ((ch (peek-next-char) (peek-next-char))) ((not (char-numeric? ch))) (next-char) (set! num-str-end (+ 1 num-str-end))) (set! params (append params (list (string->number (substring format-string num-str-beg num-str-end)))))) (set! param-value-found #t) (tilde-dispatch)) ((#\V) ; Variable parameter from next argum. (if modifier (format:error "misplaced modifier")) (set! params (append params (list (next-arg)))) (set! param-value-found #t) (tilde-dispatch)) ((#\#) ; Parameter is number of remaining args (if param-value-found (format:error "misplaced '#'")) (if modifier (format:error "misplaced modifier")) (set! params (append params (list (length (rest-args))))) (set! param-value-found #t) (tilde-dispatch)) ((#\,) ; Parameter separators (if modifier (format:error "misplaced modifier")) (if (not param-value-found) (set! params (append params '(#f)))) ; append empty paramtr (set! param-value-found #f) (tilde-dispatch)) ((#\Q) ; Inquiry messages (if (eq? modifier 'colon) (format:out-str format:version) (let ((nl (string #\newline))) (format:out-str (string-append "SLIB Common LISP format version " format:version nl " (C) copyright 1992-1994 by Dirk Lutzebaeck" nl " please send bug reports to `lutzeb@cs.tu-berlin.de'" nl)))) (anychar-dispatch)) (else ; Unknown tilde directive (format:error "unknown control character `~c'" (string-ref format-string (- format:pos 1)))))) (else (anychar-dispatch)))))) ; in case of conditional (set! format:pos 0) (set! format:arg-pos 0) (anychar-dispatch) ; start the formatting (set! format:pos recursive-pos-save) arg-pos)) ; return the position in the arg. list ;; when format:read-proof is true, format:obj->str will wrap ;; result strings starting with "#<" in an extra pair of double ;; quotes. (define format:read-proof #f) ;; format:obj->str returns a R4RS representation as a string of ;; an arbitrary scheme object. (define (format:obj->str obj slashify) (let ((res (if slashify (object->string obj) (call-with-output-string (lambda (p) (display obj p)))))) (if (and format:read-proof (string-prefix? "#<" res)) (object->string res) res))) (define format:space-ch (char->integer #\space)) (define format:zero-ch (char->integer #\0)) (define (format:par pars length index default name) (if (> length index) (let ((par (list-ref pars index))) (if par (if name (if (< par 0) (format:error "~s parameter must be a positive integer" name) par) par) default)) default)) (define (format:out-obj-padded pad-left obj slashify pars) (if (null? pars) (format:out-str (format:obj->str obj slashify)) (let ((l (length pars))) (let ((mincol (format:par pars l 0 0 "mincol")) (colinc (format:par pars l 1 1 "colinc")) (minpad (format:par pars l 2 0 "minpad")) (padchar (integer->char (format:par pars l 3 format:space-ch #f))) (objstr (format:obj->str obj slashify))) (if (not pad-left) (format:out-str objstr)) (do ((objstr-len (string-length objstr)) (i minpad (+ i colinc))) ((>= (+ objstr-len i) mincol) (format:out-fill i padchar))) (if pad-left (format:out-str objstr)))))) (define (format:out-num-padded modifier number pars radix) (if (not (integer? number)) (format:error "argument not an integer")) (let ((numstr (number->string number radix))) (if (and (null? pars) (not modifier)) (format:out-str numstr) (let ((l (length pars)) (numstr-len (string-length numstr))) (let ((mincol (format:par pars l 0 #f "mincol")) (padchar (integer->char (format:par pars l 1 format:space-ch #f))) (commachar (integer->char (format:par pars l 2 (char->integer #\,) #f))) (commawidth (format:par pars l 3 3 "commawidth"))) (if mincol (let ((numlen numstr-len)) ; calc. the output len of number (if (and (memq modifier '(at colon-at)) (>= number 0)) (set! numlen (+ numlen 1))) (if (memq modifier '(colon colon-at)) (set! numlen (+ (quotient (- numstr-len (if (< number 0) 2 1)) commawidth) numlen))) (if (> mincol numlen) (format:out-fill (- mincol numlen) padchar)))) (if (and (memq modifier '(at colon-at)) (>= number 0)) (format:out-char #\+)) (if (memq modifier '(colon colon-at)) ; insert comma character (let ((start (remainder numstr-len commawidth)) (ns (if (< number 0) 1 0))) (format:out-substr numstr 0 start) (do ((i start (+ i commawidth))) ((>= i numstr-len)) (if (> i ns) (format:out-char commachar)) (format:out-substr numstr i (+ i commawidth)))) (format:out-str numstr))))))) (define (format:tabulate modifier pars) (let ((l (length pars))) (let ((colnum (format:par pars l 0 1 "colnum")) (colinc (format:par pars l 1 1 "colinc")) (padch (integer->char (format:par pars l 2 format:space-ch #f)))) (case modifier ((colon colon-at) (format:error "unsupported modifier for ~~t")) ((at) ; relative tabulation (format:out-fill (if (= colinc 0) colnum ; colnum = colrel (do ((c 0 (+ c colinc)) (col (+ output-col colnum))) ((>= c col) (- c output-col)))) padch)) (else ; absolute tabulation (format:out-fill (cond ((< output-col colnum) (- colnum output-col)) ((= colinc 0) 0) (else (do ((c colnum (+ c colinc))) ((>= c output-col) (- c output-col))))) padch)))))) ;; roman numerals (from dorai@cs.rice.edu). (define format:roman-alist '((1000 #\M) (500 #\D) (100 #\C) (50 #\L) (10 #\X) (5 #\V) (1 #\I))) (define format:roman-boundary-values '(100 100 10 10 1 1 #f)) (define (format:num->old-roman n) (if (and (integer? n) (>= n 1)) (let loop ((n n) (romans format:roman-alist) (s '())) (if (null? romans) (list->string (reverse s)) (let ((roman-val (caar romans)) (roman-dgt (cadar romans))) (do ((q (quotient n roman-val) (- q 1)) (s s (cons roman-dgt s))) ((= q 0) (loop (remainder n roman-val) (cdr romans) s)))))) (format:error "only positive integers can be romanized"))) (define (format:num->roman n) (if (and (integer? n) (> n 0)) (let loop ((n n) (romans format:roman-alist) (boundaries format:roman-boundary-values) (s '())) (if (null? romans) (list->string (reverse s)) (let ((roman-val (caar romans)) (roman-dgt (cadar romans)) (bdry (car boundaries))) (let loop2 ((q (quotient n roman-val)) (r (remainder n roman-val)) (s s)) (if (= q 0) (if (and bdry (>= r (- roman-val bdry))) (loop (remainder r bdry) (cdr romans) (cdr boundaries) (cons roman-dgt (append (cdr (assv bdry romans)) s))) (loop r (cdr romans) (cdr boundaries) s)) (loop2 (- q 1) r (cons roman-dgt s))))))) (format:error "only positive integers can be romanized"))) ;; cardinals & ordinals (from dorai@cs.rice.edu) (define format:cardinal-ones-list '(#f "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten" "eleven" "twelve" "thirteen" "fourteen" "fifteen" "sixteen" "seventeen" "eighteen" "nineteen")) (define format:cardinal-tens-list '(#f #f "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety")) (define (format:num->cardinal999 n) ;; this procedure is inspired by the Bruno Haible's CLisp ;; function format-small-cardinal, which converts numbers ;; in the range 1 to 999, and is used for converting each ;; thousand-block in a larger number (let* ((hundreds (quotient n 100)) (tens+ones (remainder n 100)) (tens (quotient tens+ones 10)) (ones (remainder tens+ones 10))) (append (if (> hundreds 0) (append (string->list (list-ref format:cardinal-ones-list hundreds)) (string->list" hundred") (if (> tens+ones 0) '(#\space) '())) '()) (if (< tens+ones 20) (if (> tens+ones 0) (string->list (list-ref format:cardinal-ones-list tens+ones)) '()) (append (string->list (list-ref format:cardinal-tens-list tens)) (if (> ones 0) (cons #\- (string->list (list-ref format:cardinal-ones-list ones))) '())))))) (define format:cardinal-thousand-block-list '("" " thousand" " million" " billion" " trillion" " quadrillion" " quintillion" " sextillion" " septillion" " octillion" " nonillion" " decillion" " undecillion" " duodecillion" " tredecillion" " quattuordecillion" " quindecillion" " sexdecillion" " septendecillion" " octodecillion" " novemdecillion" " vigintillion")) (define (format:num->cardinal n) (cond ((not (integer? n)) (format:error "only integers can be converted to English cardinals")) ((= n 0) "zero") ((< n 0) (string-append "minus " (format:num->cardinal (- n)))) (else (let ((power3-word-limit (length format:cardinal-thousand-block-list))) (let loop ((n n) (power3 0) (s '())) (if (= n 0) (list->string s) (let ((n-before-block (quotient n 1000)) (n-after-block (remainder n 1000))) (loop n-before-block (+ power3 1) (if (> n-after-block 0) (append (if (> n-before-block 0) (string->list ", ") '()) (format:num->cardinal999 n-after-block) (if (< power3 power3-word-limit) (string->list (list-ref format:cardinal-thousand-block-list power3)) (append (string->list " times ten to the ") (string->list (format:num->ordinal (* power3 3))) (string->list " power"))) s) s))))))))) (define format:ordinal-ones-list '(#f "first" "second" "third" "fourth" "fifth" "sixth" "seventh" "eighth" "ninth" "tenth" "eleventh" "twelfth" "thirteenth" "fourteenth" "fifteenth" "sixteenth" "seventeenth" "eighteenth" "nineteenth")) (define format:ordinal-tens-list '(#f #f "twentieth" "thirtieth" "fortieth" "fiftieth" "sixtieth" "seventieth" "eightieth" "ninetieth")) (define (format:num->ordinal n) (cond ((not (integer? n)) (format:error "only integers can be converted to English ordinals")) ((= n 0) "zeroth") ((< n 0) (string-append "minus " (format:num->ordinal (- n)))) (else (let ((hundreds (quotient n 100)) (tens+ones (remainder n 100))) (string-append (if (> hundreds 0) (string-append (format:num->cardinal (* hundreds 100)) (if (= tens+ones 0) "th" " ")) "") (if (= tens+ones 0) "" (if (< tens+ones 20) (list-ref format:ordinal-ones-list tens+ones) (let ((tens (quotient tens+ones 10)) (ones (remainder tens+ones 10))) (if (= ones 0) (list-ref format:ordinal-tens-list tens) (string-append (list-ref format:cardinal-tens-list tens) "-" (list-ref format:ordinal-ones-list ones)))) ))))))) ;; format inf and nan. (define (format:out-inf-nan number width digits edigits overch padch) ;; inf and nan are always printed exactly as "+inf.0", "-inf.0" or ;; "+nan.0", suitably justified in their field. We insist on ;; printing this exact form so that the numbers can be read back in. (let* ((str (number->string number)) (len (string-length str)) (dot (string-index str #\.)) (digits (+ (or digits 0) (if edigits (+ edigits 2) 0)))) (if (and width overch (< width len)) (format:out-fill width (integer->char overch)) (let* ((leftpad (if width (max (- width (max len (+ dot 1 digits))) 0) 0)) (rightpad (if width (max (- width leftpad len) 0) 0)) (padch (integer->char (or padch format:space-ch)))) (format:out-fill leftpad padch) (format:out-str str) (format:out-fill rightpad padch))))) ;; format fixed flonums (~F) (define (format:out-fixed modifier number pars) (if (not (or (number? number) (string? number))) (format:error "argument is not a number or a number string")) (let ((l (length pars))) (let ((width (format:par pars l 0 #f "width")) (digits (format:par pars l 1 #f "digits")) (scale (format:par pars l 2 0 #f)) (overch (format:par pars l 3 #f #f)) (padch (format:par pars l 4 format:space-ch #f))) (cond ((and (number? number) (or (inf? number) (nan? number))) (format:out-inf-nan number width digits #f overch padch)) (digits (format:parse-float number #t scale) (if (<= (- format:fn-len format:fn-dot) digits) (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot))) (format:fn-round digits)) (if width (let ((numlen (+ format:fn-len 1))) (if (or (not format:fn-pos?) (eq? modifier 'at)) (set! numlen (+ numlen 1))) (if (and (= format:fn-dot 0) (> width (+ digits 1))) (set! numlen (+ numlen 1))) (if (< numlen width) (format:out-fill (- width numlen) (integer->char padch))) (if (and overch (> numlen width)) (format:out-fill width (integer->char overch)) (format:fn-out modifier (> width (+ digits 1))))) (format:fn-out modifier #t))) (else (format:parse-float number #t scale) (format:fn-strip) (if width (let ((numlen (+ format:fn-len 1))) (if (or (not format:fn-pos?) (eq? modifier 'at)) (set! numlen (+ numlen 1))) (if (= format:fn-dot 0) (set! numlen (+ numlen 1))) (if (< numlen width) (format:out-fill (- width numlen) (integer->char padch))) (if (> numlen width) ; adjust precision if possible (let ((dot-index (- numlen (- format:fn-len format:fn-dot)))) (if (> dot-index width) (if overch ; numstr too big for required width (format:out-fill width (integer->char overch)) (format:fn-out modifier #t)) (begin (format:fn-round (- width dot-index)) (format:fn-out modifier #t)))) (format:fn-out modifier #t))) (format:fn-out modifier #t))))))) ;; format exponential flonums (~E) (define (format:out-expon modifier number pars) (if (not (or (number? number) (string? number))) (format:error "argument is not a number")) (let ((l (length pars))) (let ((width (format:par pars l 0 #f "width")) (digits (format:par pars l 1 #f "digits")) (edigits (format:par pars l 2 #f "exponent digits")) (scale (format:par pars l 3 1 #f)) (overch (format:par pars l 4 #f #f)) (padch (format:par pars l 5 format:space-ch #f)) (expch (format:par pars l 6 #f #f))) (cond ((and (number? number) (or (inf? number) (nan? number))) (format:out-inf-nan number width digits edigits overch padch)) (digits ; fixed precision (let ((digits (if (> scale 0) (if (< scale (+ digits 2)) (+ (- digits scale) 1) 0) digits))) (format:parse-float number #f scale) (if (<= (- format:fn-len format:fn-dot) digits) (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot))) (format:fn-round digits)) (if width (if (and edigits overch (> format:en-len edigits)) (format:out-fill width (integer->char overch)) (let ((numlen (+ format:fn-len 3))) ; .E+ (if (or (not format:fn-pos?) (eq? modifier 'at)) (set! numlen (+ numlen 1))) (if (and (= format:fn-dot 0) (> width (+ digits 1))) (set! numlen (+ numlen 1))) (set! numlen (+ numlen (if (and edigits (>= edigits format:en-len)) edigits format:en-len))) (if (< numlen width) (format:out-fill (- width numlen) (integer->char padch))) (if (and overch (> numlen width)) (format:out-fill width (integer->char overch)) (begin (format:fn-out modifier (> width (- numlen 1))) (format:en-out edigits expch))))) (begin (format:fn-out modifier #t) (format:en-out edigits expch))))) (else (format:parse-float number #f scale) (format:fn-strip) (if width (if (and edigits overch (> format:en-len edigits)) (format:out-fill width (integer->char overch)) (let ((numlen (+ format:fn-len 3))) ; .E+ (if (or (not format:fn-pos?) (eq? modifier 'at)) (set! numlen (+ numlen 1))) (if (= format:fn-dot 0) (set! numlen (+ numlen 1))) (set! numlen (+ numlen (if (and edigits (>= edigits format:en-len)) edigits format:en-len))) (if (< numlen width) (format:out-fill (- width numlen) (integer->char padch))) (if (> numlen width) ; adjust precision if possible (let ((f (- format:fn-len format:fn-dot))) ; fract len (if (> (- numlen f) width) (if overch ; numstr too big for required width (format:out-fill width (integer->char overch)) (begin (format:fn-out modifier #t) (format:en-out edigits expch))) (begin (format:fn-round (+ (- f numlen) width)) (format:fn-out modifier #t) (format:en-out edigits expch)))) (begin (format:fn-out modifier #t) (format:en-out edigits expch))))) (begin (format:fn-out modifier #t) (format:en-out edigits expch)))))))) ;; format general flonums (~G) (define (format:out-general modifier number pars) (if (not (or (number? number) (string? number))) (format:error "argument is not a number or a number string")) (let ((l (length pars))) (let ((width (if (> l 0) (list-ref pars 0) #f)) (digits (if (> l 1) (list-ref pars 1) #f)) (edigits (if (> l 2) (list-ref pars 2) #f)) (overch (if (> l 4) (list-ref pars 4) #f)) (padch (if (> l 5) (list-ref pars 5) #f))) (cond ((and (number? number) (or (inf? number) (nan? number))) ;; FIXME: this isn't right. (format:out-inf-nan number width digits edigits overch padch)) (else (format:parse-float number #t 0) (format:fn-strip) (let* ((ee (if edigits (+ edigits 2) 4)) ; for the following algorithm (ww (if width (- width ee) #f)) ; see Steele's CL book p.395 (n (if (= format:fn-dot 0) ; number less than (abs 1.0) ? (- (format:fn-zlead)) format:fn-dot)) (d (if digits digits (max format:fn-len (min n 7)))) ; q = format:fn-len (dd (- d n))) (if (<= 0 dd d) (begin (format:out-fixed modifier number (list ww dd #f overch padch)) (format:out-fill ee #\space)) ;~@T not implemented yet (format:out-expon modifier number pars)))))))) ;; format dollar flonums (~$) (define (format:out-dollar modifier number pars) (if (not (or (number? number) (string? number))) (format:error "argument is not a number or a number string")) (let ((l (length pars))) (let ((digits (format:par pars l 0 2 "digits")) (mindig (format:par pars l 1 1 "mindig")) (width (format:par pars l 2 0 "width")) (padch (format:par pars l 3 format:space-ch #f))) (cond ((and (number? number) (or (inf? number) (nan? number))) (format:out-inf-nan number width digits #f #f padch)) (else (format:parse-float number #t 0) (if (<= (- format:fn-len format:fn-dot) digits) (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot))) (format:fn-round digits)) (let ((numlen (+ format:fn-len 1))) (if (or (not format:fn-pos?) (memq modifier '(at colon-at))) (set! numlen (+ numlen 1))) (if (and mindig (> mindig format:fn-dot)) (set! numlen (+ numlen (- mindig format:fn-dot)))) (if (and (= format:fn-dot 0) (not mindig)) (set! numlen (+ numlen 1))) (if (< numlen width) (case modifier ((colon) (if (not format:fn-pos?) (format:out-char #\-)) (format:out-fill (- width numlen) (integer->char padch))) ((at) (format:out-fill (- width numlen) (integer->char padch)) (format:out-char (if format:fn-pos? #\+ #\-))) ((colon-at) (format:out-char (if format:fn-pos? #\+ #\-)) (format:out-fill (- width numlen) (integer->char padch))) (else (format:out-fill (- width numlen) (integer->char padch)) (if (not format:fn-pos?) (format:out-char #\-)))) (if format:fn-pos? (if (memq modifier '(at colon-at)) (format:out-char #\+)) (format:out-char #\-)))) (if (and mindig (> mindig format:fn-dot)) (format:out-fill (- mindig format:fn-dot) #\0)) (if (and (= format:fn-dot 0) (not mindig)) (format:out-char #\0)) (format:out-substr format:fn-str 0 format:fn-dot) (format:out-char #\.) (format:out-substr format:fn-str format:fn-dot format:fn-len)))))) ; the flonum buffers (define format:fn-max 400) ; max. number of number digits (define format:fn-str (make-string format:fn-max)) ; number buffer (define format:fn-len 0) ; digit length of number (define format:fn-dot #f) ; dot position of number (define format:fn-pos? #t) ; number positive? (define format:en-max 10) ; max. number of exponent digits (define format:en-str (make-string format:en-max)) ; exponent buffer (define format:en-len 0) ; digit length of exponent (define format:en-pos? #t) ; exponent positive? (define (format:parse-float num fixed? scale) (let ((num-str (if (string? num) num (number->string (exact->inexact num))))) (set! format:fn-pos? #t) (set! format:fn-len 0) (set! format:fn-dot #f) (set! format:en-pos? #t) (set! format:en-len 0) (do ((i 0 (+ i 1)) (left-zeros 0) (mantissa? #t) (all-zeros? #t) (num-len (string-length num-str)) (c #f)) ; current exam. character in num-str ((= i num-len) (if (not format:fn-dot) (set! format:fn-dot format:fn-len)) (if all-zeros? (begin (set! left-zeros 0) (set! format:fn-dot 0) (set! format:fn-len 1))) ;; now format the parsed values according to format's need (if fixed? (begin ; fixed format m.nnn or .nnn (if (and (> left-zeros 0) (> format:fn-dot 0)) (if (> format:fn-dot left-zeros) (begin ; norm 0{0}nn.mm to nn.mm (format:fn-shiftleft left-zeros) (set! format:fn-dot (- format:fn-dot left-zeros)) (set! left-zeros 0)) (begin ; normalize 0{0}.nnn to .nnn (format:fn-shiftleft format:fn-dot) (set! left-zeros (- left-zeros format:fn-dot)) (set! format:fn-dot 0)))) (if (or (not (= scale 0)) (> format:en-len 0)) (let ((shift (+ scale (format:en-int)))) (cond (all-zeros? #t) ((> (+ format:fn-dot shift) format:fn-len) (format:fn-zfill #f (- shift (- format:fn-len format:fn-dot))) (set! format:fn-dot format:fn-len)) ((< (+ format:fn-dot shift) 0) (format:fn-zfill #t (- (- shift) format:fn-dot)) (set! format:fn-dot 0)) (else (if (> left-zeros 0) (if (<= left-zeros shift) ; shift always > 0 here (format:fn-shiftleft shift) ; shift out 0s (begin (format:fn-shiftleft left-zeros) (set! format:fn-dot (- shift left-zeros)))) (set! format:fn-dot (+ format:fn-dot shift)))))))) (let ((negexp ; expon format m.nnnEee (if (> left-zeros 0) (- left-zeros format:fn-dot -1) (if (= format:fn-dot 0) 1 0)))) (if (> left-zeros 0) (begin ; normalize 0{0}.nnn to n.nn (format:fn-shiftleft left-zeros) (set! format:fn-dot 1)) (if (= format:fn-dot 0) (set! format:fn-dot 1))) (format:en-set (- (+ (- format:fn-dot scale) (format:en-int)) negexp)) (cond (all-zeros? (format:en-set 0) (set! format:fn-dot 1)) ((< scale 0) ; leading zero (format:fn-zfill #t (- scale)) (set! format:fn-dot 0)) ((> scale format:fn-dot) (format:fn-zfill #f (- scale format:fn-dot)) (set! format:fn-dot scale)) (else (set! format:fn-dot scale))))) #t) ;; do body (set! c (string-ref num-str i)) ; parse the output of number->string (cond ; which can be any valid number ((char-numeric? c) ; representation of R4RS except (if mantissa? ; complex numbers (begin (if (char=? c #\0) (if all-zeros? (set! left-zeros (+ left-zeros 1))) (begin (set! all-zeros? #f))) (string-set! format:fn-str format:fn-len c) (set! format:fn-len (+ format:fn-len 1))) (begin (string-set! format:en-str format:en-len c) (set! format:en-len (+ format:en-len 1))))) ((or (char=? c #\-) (char=? c #\+)) (if mantissa? (set! format:fn-pos? (char=? c #\+)) (set! format:en-pos? (char=? c #\+)))) ((char=? c #\.) (set! format:fn-dot format:fn-len)) ((char=? c #\e) (set! mantissa? #f)) ((char=? c #\E) (set! mantissa? #f)) ((char-whitespace? c) #t) ((char=? c #\d) #t) ; decimal radix prefix ((char=? c #\#) #t) (else (format:error "illegal character `~c' in number->string" c)))))) (define (format:en-int) ; convert exponent string to integer (if (= format:en-len 0) 0 (do ((i 0 (+ i 1)) (n 0)) ((= i format:en-len) (if format:en-pos? n (- n))) (set! n (+ (* n 10) (- (char->integer (string-ref format:en-str i)) format:zero-ch)))))) (define (format:en-set en) ; set exponent string number (set! format:en-len 0) (set! format:en-pos? (>= en 0)) (let ((en-str (number->string en))) (do ((i 0 (+ i 1)) (en-len (string-length en-str)) (c #f)) ((= i en-len)) (set! c (string-ref en-str i)) (if (char-numeric? c) (begin (string-set! format:en-str format:en-len c) (set! format:en-len (+ format:en-len 1))))))) (define (format:fn-zfill left? n) ; fill current number string with 0s (if (> (+ n format:fn-len) format:fn-max) ; from the left or right (format:error "number is too long to format (enlarge format:fn-max)")) (set! format:fn-len (+ format:fn-len n)) (if left? (do ((i format:fn-len (- i 1))) ; fill n 0s to left ((< i 0)) (string-set! format:fn-str i (if (< i n) #\0 (string-ref format:fn-str (- i n))))) (do ((i (- format:fn-len n) (+ i 1))) ; fill n 0s to the right ((= i format:fn-len)) (string-set! format:fn-str i #\0)))) (define (format:fn-shiftleft n) ; shift left current number n positions (if (> n format:fn-len) (format:error "internal error in format:fn-shiftleft (~d,~d)" n format:fn-len)) (do ((i n (+ i 1))) ((= i format:fn-len) (set! format:fn-len (- format:fn-len n))) (string-set! format:fn-str (- i n) (string-ref format:fn-str i)))) (define (format:fn-round digits) ; round format:fn-str (set! digits (+ digits format:fn-dot)) (do ((i digits (- i 1)) ; "099",2 -> "10" (c 5)) ; "023",2 -> "02" ((or (= c 0) (< i 0)) ; "999",2 -> "100" (if (= c 1) ; "005",2 -> "01" (begin ; carry overflow (set! format:fn-len digits) (format:fn-zfill #t 1) ; add a 1 before fn-str (string-set! format:fn-str 0 #\1) (set! format:fn-dot (+ format:fn-dot 1))) (set! format:fn-len digits))) (set! c (+ (- (char->integer (string-ref format:fn-str i)) format:zero-ch) c)) (string-set! format:fn-str i (integer->char (if (< c 10) (+ c format:zero-ch) (+ (- c 10) format:zero-ch)))) (set! c (if (< c 10) 0 1)))) (define (format:fn-out modifier add-leading-zero?) (if format:fn-pos? (if (eq? modifier 'at) (format:out-char #\+)) (format:out-char #\-)) (if (= format:fn-dot 0) (if add-leading-zero? (format:out-char #\0)) (format:out-substr format:fn-str 0 format:fn-dot)) (format:out-char #\.) (format:out-substr format:fn-str format:fn-dot format:fn-len)) (define (format:en-out edigits expch) (format:out-char (if expch (integer->char expch) #\E)) (format:out-char (if format:en-pos? #\+ #\-)) (if edigits (if (< format:en-len edigits) (format:out-fill (- edigits format:en-len) #\0))) (format:out-substr format:en-str 0 format:en-len)) (define (format:fn-strip) ; strip trailing zeros but one (string-set! format:fn-str format:fn-len #\0) (do ((i format:fn-len (- i 1))) ((or (not (char=? (string-ref format:fn-str i) #\0)) (<= i format:fn-dot)) (set! format:fn-len (+ i 1))))) (define (format:fn-zlead) ; count leading zeros (do ((i 0 (+ i 1))) ((or (= i format:fn-len) (not (char=? (string-ref format:fn-str i) #\0))) (if (= i format:fn-len) ; found a real zero 0 i)))) ;;; some global functions not found in SLIB (define (string-capitalize-first str) ; "hello" -> "Hello" (let ((cap-str (string-copy str)) ; "hELLO" -> "Hello" (non-first-alpha #f) ; "*hello" -> "*Hello" (str-len (string-length str))) ; "hello you" -> "Hello you" (do ((i 0 (+ i 1))) ((= i str-len) cap-str) (let ((c (string-ref str i))) (if (char-alphabetic? c) (if non-first-alpha (string-set! cap-str i (char-downcase c)) (begin (set! non-first-alpha #t) (string-set! cap-str i (char-upcase c))))))))) ;; Aborts the program when a formatting error occures. This is a null ;; argument closure to jump to the interpreters toplevel continuation. (define (format:abort) (error "error in format")) (let ((arg-pos (format:format-work format-string format-args)) (arg-len (length format-args))) (cond ((> arg-pos arg-len) (set! format:arg-pos (+ arg-len 1)) (display format:arg-pos) (format:error "~a missing argument~:p" (- arg-pos arg-len))) (else (if flush-output? (force-output port)) (if destination #t (let ((str (get-output-string port))) (close-port port) str))))))) (begin-deprecated (set! format (let ((format format)) (case-lambda ((destination format-string . args) (if (string? destination) (begin (issue-deprecation-warning "Omitting the destination on a call to format is deprecated." "Pass #f as the destination, before the format string.") (apply format #f destination format-string args)) (apply format destination format-string args))) ((deprecated-format-string-only) (issue-deprecation-warning "Omitting the destination port on a call to format is deprecated." "Pass #f as the destination port, before the format string.") (format #f deprecated-format-string-only)))))) ;; Thanks to Shuji Narazaki (module-set! the-root-module 'format format)