;;;; "printf.scm" Implementation of standard C functions for Scheme ;;; Copyright (C) 1991-1993, 1996 Aubrey Jaffer. ; ;Permission to copy this software, to redistribute it, and to use it ;for any purpose is granted, subject to the following restrictions and ;understandings. ; ;1. Any copy made of this software must include this copyright notice ;in full. ; ;2. I have made no warrantee or representation that the operation of ;this software will be error-free, and I am under no obligation to ;provide any services, by way of maintenance, update, or otherwise. ; ;3. In conjunction with products arising from the use of this ;material, there shall be no use of my name in any advertising, ;promotional, or sales literature without prior written consent in ;each case. (require 'string-case) ;; Parse the output of NUMBER->STRING. ;; Returns a list: (sign-character digit-string exponent-integer) ;; SIGN-CHAR will be either #\+ or #\-, DIGIT-STRING will always begin ;; with a "0", after which a decimal point should be understood. ;; If STR denotes a non-real number, 3 additional elements for the ;; complex part are appended. (define (stdio:parse-float str) (let ((n (string-length str)) (iend 0)) (letrec ((prefix (lambda (i rest) (if (and (< i (- n 1)) (char=? #\# (string-ref str i))) (case (string-ref str (+ i 1)) ((#\d #\i #\e) (prefix (+ i 2) rest)) ((#\.) (rest i)) (else (parse-error))) (rest i)))) (sign (lambda (i rest) (if (< i n) (let ((c (string-ref str i))) (case c ((#\- #\+) (cons c (rest (+ i 1)))) (else (cons #\+ (rest i)))))))) (digits (lambda (i rest) (do ((j i (+ j 1))) ((or (>= j n) (not (or (char-numeric? (string-ref str j)) (char=? #\# (string-ref str j))))) (cons (if (= i j) "0" (substring str i j)) (rest j)))))) (point (lambda (i rest) (if (and (< i n) (char=? #\. (string-ref str i))) (rest (+ i 1)) (rest i)))) (exp (lambda (i) (if (< i n) (case (string-ref str i) ((#\e #\s #\f #\d #\l #\E #\S #\F #\D #\L) (let ((s (sign (+ i 1) (lambda (i) (digits i end!))))) (list (if (char=? #\- (car s)) (- (string->number (cadr s))) (string->number (cadr s)))))) (else (end! i) '(0))) (begin (end! i) '(0))))) (end! (lambda (i) (set! iend i) '())) (real (lambda (i) (let ((parsed (prefix i (lambda (i) (sign i (lambda (i) (digits i (lambda (i) (point i (lambda (i) (digits i exp))))))))))) (and (list? parsed) (apply (lambda (sgn idigs fdigs exp) (let* ((digs (string-append "0" idigs fdigs)) (n (string-length digs))) (let loop ((i 1) (exp (+ exp (string-length idigs)))) (if (and (< i n) (char=? #\0 (string-ref digs i))) (loop (+ i 1) (- exp 1)) (list sgn (substring digs (- i 1) n) exp))))) parsed))))) (parse-error (lambda () #f))) (let ((realpart (real 0))) (cond ((= iend n) realpart) ((memv (string-ref str iend) '(#\+ #\-)) (let ((complexpart (real iend))) (and (= iend (- n 1)) (char-ci=? #\i (string-ref str iend)) (append realpart complexpart)))) ((eqv? (string-ref str iend) #\@) ;; Polar form: No point in parsing the angle ourselves, ;; since some transcendental approximation is unavoidable. (let ((num (string->number str))) (and num (let ((realpart (stdio:parse-float (number->string (real-part num)))) (imagpart (if (real? num) '() (stdio:parse-float (number->string (imag-part num)))))) (and realpart imagpart (append realpart imagpart)))))) (else #f)))))) ;; STR is a digit string representing a floating point mantissa, STR must ;; begin with "0", after which a decimal point is understood. ;; The output is a digit string rounded to NDIGS digits after the decimal ;; point implied between chars 0 and 1. ;; If STRIP-0S is not #F then trailing zeros will be stripped from the result. ;; In this case, STRIP-0S should be the minimum number of digits required ;; after the implied decimal point. (define (stdio:round-string str ndigs strip-0s) (let* ((n (- (string-length str) 1)) (res (cond ((< ndigs 0) "") ((= n ndigs) str) ((< n ndigs) (let ((zeropad (make-string (max 0 (- (or strip-0s ndigs) n)) (if (char-numeric? (string-ref str n)) #\0 #\#)))) (if (zero? (string-length zeropad)) str (string-append str zeropad)))) (else (let ((res (substring str 0 (+ ndigs 1))) (dig (lambda (i) (let ((c (string-ref str i))) (if (char-numeric? c) (string->number (string c)) 0))))) (let ((ldig (dig (+ 1 ndigs)))) (if (or (> ldig 5) (and (= ldig 5) (let loop ((i (+ 2 ndigs))) (if (> i n) (odd? (dig ndigs)) (if (zero? (dig i)) (loop (+ i 1)) #t))))) (let inc! ((i ndigs)) (let ((d (dig i))) (if (< d 9) (string-set! res i (string-ref (number->string (+ d 1)) 0)) (begin (string-set! res i #\0) (inc! (- i 1)))))))) res))))) (if strip-0s (let loop ((i (- (string-length res) 1))) (if (or (<= i strip-0s) (not (char=? #\0 (string-ref res i)))) (substring res 0 (+ i 1)) (loop (- i 1)))) res))) (define (stdio:iprintf out format-string . args) (cond ((not (equal? "" format-string)) (let ((pos -1) (fl (string-length format-string)) (fc (string-ref format-string 0))) (define (advance) (set! pos (+ 1 pos)) (cond ((>= pos fl) (set! fc #f)) (else (set! fc (string-ref format-string pos))))) (define (must-advance) (set! pos (+ 1 pos)) (cond ((>= pos fl) (incomplete)) (else (set! fc (string-ref format-string pos))))) (define (end-of-format?) (>= pos fl)) (define (incomplete) (slib:error 'printf "conversion specification incomplete" format-string)) (define (wna) (slib:error 'printf "wrong number of arguments" (length args) format-string)) (let loop ((args args)) (advance) (cond ((end-of-format?) ;;(or (null? args) (wna)) ;Extra arguments are *not* a bug. ) ((eqv? #\\ fc);;Emulating C strings may not be a good idea. (must-advance) (and (case fc ((#\n #\N) (out #\newline)) ((#\t #\T) (out slib:tab)) ;;((#\r #\R) (out #\return)) ((#\f #\F) (out slib:form-feed)) ((#\newline) #t) (else (out fc))) (loop args))) ((eqv? #\% fc) (must-advance) (let ((left-adjust #f) ;- (signed #f) ;+ (blank #f) (alternate-form #f) ;# (leading-0s #f) ;0 (width 0) (precision -1) (type-modifier #f) (read-format-number (lambda () (cond ((eqv? #\* fc) ; GNU extension (must-advance) (let ((ans (car args))) (set! args (cdr args)) ans)) (else (do ((c fc fc) (accum 0 (+ (* accum 10) (string->number (string c))))) ((not (char-numeric? fc)) accum) (must-advance))))))) (define (pad pre . strs) (let loop ((len (string-length pre)) (ss strs)) (cond ((>= len width) (apply string-append pre strs)) ((null? ss) (cond (left-adjust (apply string-append pre (append strs (list (make-string (- width len) #\space))))) (leading-0s (apply string-append pre (make-string (- width len) #\0) strs)) (else (apply string-append (make-string (- width len) #\space) pre strs)))) (else (loop (+ len (string-length (car ss))) (cdr ss)))))) (define integer-convert (lambda (s radix) (cond ((not (negative? precision)) (set! leading-0s #f) (if (and (zero? precision) (eqv? 0 s)) (set! s "")))) (set! s (cond ((symbol? s) (symbol->string s)) ((number? s) (number->string s radix)) ((or (not s) (null? s)) "0") ((string? s) s) (else "1"))) (let ((pre (cond ((equal? "" s) "") ((eqv? #\- (string-ref s 0)) (set! s (substring s 1 (string-length s))) "-") (signed "+") (blank " ") (alternate-form (case radix ((8) "0") ((16) "0x") (else ""))) (else "")))) (pad pre (if (< (string-length s) precision) (make-string (- precision (string-length s)) #\0) "") s)))) (define (float-convert num fc) (define (f digs exp strip-0s) (let ((digs (stdio:round-string digs (+ exp precision) (and strip-0s exp)))) (cond ((>= exp 0) (let* ((i0 (cond ((zero? exp) 0) ((char=? #\0 (string-ref digs 0)) 1) (else 0))) (i1 (max 1 (+ 1 exp))) (idigs (substring digs i0 i1)) (fdigs (substring digs i1 (string-length digs)))) (cons idigs (if (and (string=? fdigs "") (not alternate-form)) '() (list "." fdigs))))) ((zero? precision) (list (if alternate-form "0." "0"))) ((and strip-0s (string=? digs "") (list "0"))) (else (list "0." (make-string (min precision (- -1 exp)) #\0) digs))))) (define (e digs exp strip-0s) (let* ((digs (stdio:round-string digs (+ 1 precision) (and strip-0s 0))) (istrt (if (char=? #\0 (string-ref digs 0)) 1 0)) (fdigs (substring digs (+ 1 istrt) (string-length digs))) (exp (if (zero? istrt) exp (- exp 1)))) (list (substring digs istrt (+ 1 istrt)) (if (and (string=? fdigs "") (not alternate-form)) "" ".") fdigs (if (char-upper-case? fc) "E" "e") (if (negative? exp) "-" "+") (if (< -10 exp 10) "0" "") (number->string (abs exp))))) (define (g digs exp) (let ((strip-0s (not alternate-form))) (set! alternate-form #f) (cond ((<= (- 1 precision) exp precision) (set! precision (- precision exp)) (f digs exp strip-0s)) (else (set! precision (- precision 1)) (e digs exp strip-0s))))) (define (k digs exp sep) (let* ((units '#("y" "z" "a" "f" "p" "n" "u" "m" "" "k" "M" "G" "T" "P" "E" "Z" "Y")) (base 8) ;index of "" (uind (let ((i (if (negative? exp) (quotient (- exp 3) 3) (quotient (- exp 1) 3)))) (and (< -1 (+ i base) (vector-length units)) i)))) (cond (uind (set! exp (- exp (* 3 uind))) (set! precision (max 0 (- precision exp))) (append (f digs exp #f) (list sep (vector-ref units (+ uind base))))) (else (g digs exp))))) (cond ((negative? precision) (set! precision 6)) ((and (zero? precision) (char-ci=? fc #\g)) (set! precision 1))) (let* ((str (cond ((number? num) (number->string (exact->inexact num))) ((string? num) num) ((symbol? num) (symbol->string num)) (else "???"))) (parsed (stdio:parse-float str))) (letrec ((format-real (lambda (signed? sgn digs exp . rest) (if (null? rest) (cons (if (char=? #\- sgn) "-" (if signed? "+" (if blank " " ""))) (case fc ((#\e #\E) (e digs exp #f)) ((#\f #\F) (f digs exp #f)) ((#\g #\G) (g digs exp)) ((#\k) (k digs exp "")) ((#\K) (k digs exp " ")))) (append (format-real signed? sgn digs exp) (apply format-real #t rest) '("i")))))) (if parsed (apply pad (apply format-real signed parsed)) (pad "???"))))) (do () ((case fc ((#\-) (set! left-adjust #t) #f) ((#\+) (set! signed #t) #f) ((#\ ) (set! blank #t) #f) ((#\#) (set! alternate-form #t) #f) ((#\0) (set! leading-0s #t) #f) (else #t))) (must-advance)) (cond (left-adjust (set! leading-0s #f))) (cond (signed (set! blank #f))) (set! width (read-format-number)) (cond ((negative? width) (set! left-adjust #t) (set! width (- width)))) (cond ((eqv? #\. fc) (must-advance) (set! precision (read-format-number)))) (case fc ;Ignore these specifiers ((#\l #\L #\h) (set! type-modifier fc) (must-advance))) ;;At this point fc completely determines the format to use. (if (null? args) (if (memv (char-downcase fc) '(#\c #\s #\a #\d #\i #\u #\o #\x #\b #\f #\e #\g #\k)) (wna))) (case fc ;; only - is allowed between % and c ((#\c #\C) ; C is enhancement (and (out (string (car args))) (loop (cdr args)))) ;; only - flag, no type-modifiers ((#\s #\S) ; S is enhancement (let ((s (cond ((symbol? (car args)) (symbol->string (car args))) ((not (car args)) "(NULL)") (else (car args))))) (cond ((not (or (negative? precision) (>= precision (string-length s)))) (set! s (substring s 0 precision)))) (and (out (cond ((<= width (string-length s)) s) (left-adjust (string-append s (make-string (- width (string-length s)) #\ ))) (else (string-append (make-string (- width (string-length s)) (if leading-0s #\0 #\ )) s)))) (loop (cdr args))))) ;; SLIB extension ((#\a #\A) ;#\a #\A are pretty-print (require 'generic-write) (let ((os "") (pr precision)) (generic-write (car args) (not alternate-form) #f (cond ((and left-adjust (negative? pr)) (set! pr 0) (lambda (s) (set! pr (+ pr (string-length s))) (out s))) (left-adjust (lambda (s) (define sl (- pr (string-length s))) (set! pr (cond ((negative? sl) (out (substring s 0 pr)) 0) (else (out s) sl))) (positive? sl))) ((negative? pr) (set! pr width) (lambda (s) (set! pr (- pr (string-length s))) (cond ((not os) (out s)) ((negative? pr) (out os) (set! os #f) (out s)) (else (set! os (string-append os s)))) #t)) (else (lambda (s) (define sl (- pr (string-length s))) (cond ((negative? sl) (set! os (string-append os (substring s 0 pr)))) (else (set! os (string-append os s)))) (set! pr sl) (positive? sl))))) (cond ((and left-adjust (negative? precision)) (cond ((> width pr) (out (make-string (- width pr) #\ ))))) (left-adjust (cond ((> width (- precision pr)) (out (make-string (- width (- precision pr)) #\ ))))) ((not os)) ((<= width (string-length os)) (out os)) (else (and (out (make-string (- width (string-length os)) #\ )) (out os))))) (loop (cdr args))) ((#\d #\D #\i #\I #\u #\U) (and (out (integer-convert (car args) 10)) (loop (cdr args)))) ((#\o #\O) (and (out (integer-convert (car args) 8)) (loop (cdr args)))) ((#\x #\X) (and (out ((if (char-upper-case? fc) string-upcase string-downcase) (integer-convert (car args) 16))) (loop (cdr args)))) ((#\b #\B) (and (out (integer-convert (car args) 2)) (loop (cdr args)))) ((#\%) (and (out #\%) (loop args))) ((#\f #\F #\e #\E #\g #\G #\k #\K) (and (out (float-convert (car args) fc)) (loop (cdr args)))) (else (cond ((end-of-format?) (incomplete)) (else (and (out #\%) (out fc) (out #\?) (loop args)))))))) (else (and (out fc) (loop args))))))))) (define (stdio:fprintf port format . args) (let ((cnt 0)) (apply stdio:iprintf (lambda (x) (cond ((string? x) (set! cnt (+ (string-length x) cnt)) (display x port) #t) (else (set! cnt (+ 1 cnt)) (display x port) #t))) format args) cnt)) (define (stdio:printf format . args) (apply stdio:fprintf (current-output-port) format args)) (define (stdio:sprintf str format . args) (let* ((cnt 0) (s (cond ((string? str) str) ((number? str) (make-string str)) ((not str) (make-string 100)) (else (slib:error 'sprintf "first argument not understood" str)))) (end (string-length s))) (apply stdio:iprintf (lambda (x) (cond ((string? x) (if (or str (>= (- end cnt) (string-length x))) (do ((lend (min (string-length x) (- end cnt))) (i 0 (+ i 1))) ((>= i lend)) (string-set! s cnt (string-ref x i)) (set! cnt (+ cnt 1))) (let () (set! s (string-append (substring s 0 cnt) x)) (set! cnt (string-length s)) (set! end cnt)))) ((and str (>= cnt end))) (else (cond ((and (not str) (>= cnt end)) (set! s (string-append s (make-string 100))) (set! end (string-length s)))) (string-set! s cnt (if (char? x) x #\?)) (set! cnt (+ cnt 1)))) (not (and str (>= cnt end)))) format args) (cond ((string? str) cnt) ((eqv? end cnt) s) (else (substring s 0 cnt))))) (define printf stdio:printf) (define fprintf stdio:fprintf) (define sprintf stdio:sprintf) ;;(do ((i 0 (+ 1 i))) ((> i 50)) (printf "%s\n" (sprintf i "%#-13a:%#13a:%-13.8a:" "123456789" "123456789" "123456789")))