;;; format.ss ;;; Copyright 1984-2017 Cisco Systems, Inc. ;;; ;;; Licensed under the Apache License, Version 2.0 (the "License"); ;;; you may not use this file except in compliance with the License. ;;; You may obtain a copy of the License at ;;; ;;; http://www.apache.org/licenses/LICENSE-2.0 ;;; ;;; Unless required by applicable law or agreed to in writing, software ;;; distributed under the License is distributed on an "AS IS" BASIS, ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ;;; See the License for the specific language governing permissions and ;;; limitations under the License. #| TODO: * more tests - tests of format with #f, #t, or port as first argument; test of printf and fprintf, tests that exercise all paths of cp1in format handler - verify complete coverage of code paths - extract all tests from cltl2 - more # and v parameter tests - ~^ tests: - need tests for outside abort, abort in indirect, nested {super-,}abort in conditionals, nested {super-,}abort in case-conversion, etc. - need tests with one parameter and two parameters - ~* and ~:p tests for moving around loop args - test float printing with Bob's set of floats * use something better than string-append for constructing ~f and ~e output * use more efficient dispatch, e.g., have case use binary search for fixnum keys; or modify compiler to use jump tables for well-behaved case's * look into not hardcoding float-base = 10 * vparams adds substantial allocation overhead, probably because of the compiler's handling of mvlet producers containing if expressions; fix the compiler * abstract out Chez Scheme specifics, like display-string, $list-length, string ports, use of generic port |# ;;; missing directives ;;; pretty-printer controls (^_, ~:>, ~i, ~:t ~/name/) ;;; known incompatibilities with Common Lisp ;;; : [print nil as ()] modifier ignored for ~a ;;; : [print nil as ()] modifier treated as "print-gensym #f" for ~s ;;; common lisp doesn't complain when there are unused arguments, ;;; may not complain when there are too few arguments. we always ;;; complain when there are too few and complain when we can determine ;;; statically that there are too many ;;; we insist on real argument for ~f, ~e, and ~g; common lisp is ;;; lax and sends off anything else to ~D. ;;; other notes ;;; we always assume that format starts at the beginning of a line ;;; in support of ~&, ~t, and ~<...> (let () ;;; configuration ;; check for too many args at parse time (define static-too-many-args-check #t) ;; check for too many args at parse time for indirects and loop bodies (define indirect-too-many-args-check #f) ;; check for too many args at run time. the check is always suppressed ;; when we terminate a format or indirect format as the result of ~^ (define dynamic-too-many-args-check #f) ;;; predicates used to check format parameters (define nnfixnum? (lambda (x) (and (fixnum? x) (fx>= x 0)))) (define true? (lambda (x) #t)) (define pfixnum? (lambda (x) (and (fixnum? x) (fx> x 0)))) (define radix? (lambda (x) (and (fixnum? x) (fx<= 2 x 36)))) ; we require nongenerative records because the compiler embeds parsed ; format strings in object files. force cp1in-parse-format to return #f ; to bootstrap after making changes to any of these records (define-datatype (#{fmt cgos0c9ufi1rq-fd} (immutable directive)) (#{newline cgos0c9ufi1rq-ez} n) (#{fresh-line cgos0c9ufi1rq-fc} n) (#{dup-char cgos0c9ufi1rq-fh} n c) (#{display cgos0c9ufi1rq-fi} mincol colinc minpad pad-char left?) (#{simple-display cgos0c9ufi1rq-et}) (#{simple-write cgos0c9ufi1rq-es}) (#{write cgos0c9ufi1rq-ei} mincol colinc minpad pad-char nogensym? left?) (#{cwrite cgos0c9ufi1rq-fk} colon? at?) (#{fwrite cgos0c9ufi1rq-fb} w d k oc pc sign?) (#{ewrite cgos0c9ufi1rq-ff} w d ew k oc pc ec sign?) (#{gwrite cgos0c9ufi1rq-e9} w d ew k oc pc ec sign?) (#{$write cgos0c9ufi1rq-eg} d n w pc sign-before-pad? sign?) (#{write-radix cgos0c9ufi1rq-eh} base w pc cc ci sign? commas?) (#{plural cgos0c9ufi1rq-ey} back-up? y/ies?) (#{fancy-radix cgos0c9ufi1rq-fe} colon? at?) (#{indirect cgos0c9ufi1rq-e6} splice?) (#{goto cgos0c9ufi1rq-fa} n reverse? absolute?) (#{tabulate cgos0c9ufi1rq-ek} colnum colinc relative?) (#{convert-case cgos0c9ufi1rq-fl} nested-cmd* colon? at?) (#{conditional cgos0c9ufi1rq-fo} n cases default) (#{conditional/at cgos0c9ufi1rq-fn} consequent) (#{conditional/colon cgos0c9ufi1rq-fm} alternative consequent) (#{justify cgos0c9ufi1rq-e1} mincol colinc minpad pad-char before? after? initial margin columns segments) (#{abort cgos0c9ufi1rq-ft} n m super?) (#{iteration cgos0c9ufi1rq-e2} body n sublists? use-remaining? at-least-once?) (#{columntrack cgos0c9ufi1rq-fq} body) ) ;;; parse string to list of strings, chars, and fmt records (define parse (lambda (who cntl) (define column? #f) (define-syntactic-monad state nargs cmd* stack) (define-record-type frame (fields (immutable directive) (immutable cmd*)) (nongenerative)) (define-record-type cvtcase-frame (parent frame) (fields (immutable colon?) (immutable at?)) (nongenerative) (sealed #t)) (define-record-type conditional/at-frame (parent frame) (nongenerative) (sealed #t)) (define-record-type conditional/colon-frame (parent frame) (fields (mutable altern)) (nongenerative) (sealed #t) (protocol (lambda (make-new) (lambda (directive cmd*) ((make-new directive cmd*) #f))))) (define-record-type conditional-frame (parent frame) (fields (immutable n) (mutable cases) (mutable default?)) (nongenerative) (sealed #t) (protocol (lambda (make-new) (lambda (directive cmd* n) ((make-new directive cmd*) n '() #f))))) (define-record-type justify-frame (parent frame) (fields (immutable mincol) (immutable colinc) (immutable minpad) (immutable pc) (immutable before?) (immutable after?) (mutable segments) (mutable initial) (mutable margin) (mutable columns)) (nongenerative) (sealed #t) (protocol (lambda (make-new) (lambda (directive cmd* mincol colinc minpad pc before? after?) ((make-new directive cmd*) mincol colinc minpad pc before? after? '() #f #f #f))))) (define-record-type iteration-frame (parent frame) (fields (immutable n) (immutable sublists?) (immutable use-remaining?)) (nongenerative) (sealed #t)) (define incomplete-format-directive (lambda (b i) ($oops who "incomplete format directive ~s" (substring cntl b i)))) (define (bump x n) (and x n (fx+ x n))) (unless (string? cntl) ($oops who "~s is not a string" cntl)) (let ([nmax (fx- (string-length cntl) 1)]) (define char (lambda (i) (if (fx> i nmax) #!eof (string-ref cntl i)))) (define sfinal (state lambda () (unless (null? stack) ($oops who "unclosed directive ~a" (frame-directive (car stack)))) (let ([cmd* (reverse cmd*)]) (values (if column? (list (fmt-columntrack "" cmd*)) cmd*) nargs)))) (define s0 (state lambda (i) (let ([c (char i)]) (state-case c [eof (state sfinal ())] [(#\~) (state s3 () (fx+ i 1) i)] [else (state s1 () (fx+ i 1) i c)])))) (define s1 (state lambda (i b c0) (let ([c (char i)]) (state-case c [eof (state sfinal ([cmd* (cons c0 cmd*)]))] [(#\~) (state s3 ([cmd* (cons c0 cmd*)]) (fx+ i 1) i)] [else (state s2 () (fx+ i 1) b)])))) (define s2 (state lambda (i b) (let ([c (char i)]) (state-case c [eof (state sfinal ([cmd* (cons (substring cntl b i) cmd*)]))] [(#\~) (state s3 ([cmd* (cons (substring cntl b i) cmd*)]) (fx+ i 1) i)] [else (state s2 () (fx+ i 1) b)])))) (define s3 (state lambda (i b) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [(#\~) (state s1 () (fx+ i 1) i #\~)] [(#\- #\+) (state s4-sign () (fx+ i 1) b '() i)] [((#\0 - #\9)) (state s4-digit () (fx+ i 1) b '() i)] [(#\,) (state s4-comma () (fx+ i 1) b '(#f))] [(#\') (state s4-quote () (fx+ i 1) b '())] [(#\#) (state s4-after-param () (fx+ i 1) b '(hash))] [(#\v) (state s4-after-param ([nargs (bump nargs 1)]) (fx+ i 1) b '(v))] [else (state s5 () i b '())])))) (define s4-sign (state lambda (i b p* bp) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [((#\0 - #\9)) (state s4-digit () (fx+ i 1) b p* bp)] [else (incomplete-format-directive b i)])))) (define s4-quote (state lambda (i b p*) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [else (state s4-after-param () (fx+ i 1) b (cons c p*))])))) (define s4-after-param (state lambda (i b p*) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [(#\,) (state s4-comma () (fx+ i 1) b p*)] [else (state s5 () i b (reverse p*))])))) (define s4-digit (state lambda (i b p* bp) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [((#\0 - #\9)) (state s4-digit () (fx+ i 1) b p* bp)] [(#\,) (state s4-comma () (fx+ i 1) b (cons (string->number (substring cntl bp i)) p*))] [else (state s5 () i b (reverse (cons (string->number (substring cntl bp i)) p*)))])))) (define s4-comma (state lambda (i b p*) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [(#\- #\+) (state s4-sign () (fx+ i 1) b p* i)] [((#\0 - #\9)) (state s4-digit () (fx+ i 1) b p* i)] [(#\,) (state s4-comma () (fx+ i 1) b (cons #f p*))] [(#\') (state s4-quote () (fx+ i 1) b p*)] [(#\#) (state s4-after-param () (fx+ i 1) b (cons 'hash p*))] [(#\v) (state s4-after-param ([nargs (bump nargs 1)]) (fx+ i 1) b (cons 'v p*))] [else (state s5 () i b (reverse (cons #f p*)))])))) (define s5 (state lambda (i b p*) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [(#\:) (state s5-colon () (fx+ i 1) b p*)] [(#\@) (state s5-at () (fx+ i 1) b p*)] [else (state s6 () i b p* #f #f)])))) (define s5-colon (state lambda (i b p*) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [(#\@) (state s6 () (fx+ i 1) b p* #t #t)] [else (state s6 () i b p* #t #f)])))) (define s5-at (state lambda (i b p*) (let ([c (char i)]) (state-case c [eof (incomplete-format-directive b i)] [(#\:) (state s6 () (fx+ i 1) b p* #t #t)] [else (state s6 () i b p* #f #t)])))) (define s6 (state lambda (i b p* colon? at?) (define skip-non-newline-white (lambda (i) (let ([c (char i)]) (state-case c [eof i] [(#\space #\tab #\page #\return) (skip-non-newline-white (fx+ i 1))] [else i])))) (let ([c (char i)]) (define no-colon (lambda () (when colon? ($oops who "~~~c directive has no : flag" c)))) (define no-at (lambda () (when at? ($oops who "~~~c directive has no @ flag" c)))) (define too-many-parameters (lambda () ($oops who "too many parameters in ~~~c directive ~s" c (substring cntl b (fx+ i 1))))) (define missing-parameter (lambda (what) ($oops who "missing required ~s parameter in ~~~c directive ~s" what c (substring cntl b (fx+ i 1))))) (define invalid-parameter (lambda (what arg) ($oops who "invalid ~s parameter ~a in ~~~c directive ~s" what arg c (substring cntl b (fx+ i 1))))) (define misplaced-directive (lambda () ($oops who "misplaced directive ~s" (substring cntl b (fx+ i 1))))) (define-syntax parameters (lambda (x) (define process-param (lambda (t* param* body) (if (null? param*) body (with-syntax ([body (process-param (cdr t*) (cdr param*) body)] [t (car t*)]) (syntax-case (car param*) (implicit) [(implicit e) #'(let ([t e]) body)] [(type? p) #'(begin (when (null? p*) (missing-parameter 'p)) (let ([t (car p*)] [p* (cdr p*)]) (when (not t) (missing-parameter 'p)) (unless (or (type? t) (memq t '(hash v))) (invalid-parameter 'p t)) body))] [(type? p default) #'(let ([proc (lambda (t p*) body)]) (if (null? p*) (proc 'default p*) (let ([t (car p*)] [p* (cdr p*)]) (if (not t) (proc default p*) (begin (unless (or (type? t) (memq t '(hash v))) (invalid-parameter 'p t)) (proc t p*))))))]))))) (syntax-case x () [(_ ([t param] ...) e1 e2 ...) (process-param #'(t ...) #'(param ...) #'(begin (unless (null? p*) (too-many-parameters)) (let () e1 e2 ...)))]))) (define-syntax directive (lambda (x) (define construct-name (lambda (template-identifier . args) (datum->syntax template-identifier (string->symbol (apply string-append (map (lambda (x) (if (string? x) x (symbol->string (syntax->datum x)))) args)))))) (syntax-case x () [(k (d param ...) n) (with-syntax ([(t ...) (generate-temporaries #'(param ...))] [fmt-d (construct-name #'d "fmt-" #'d)]) (with-implicit (k state cmd* nargs) #'(parameters ([t param] ...) (state s0 ([cmd* (cons (fmt-d (substring cntl b (fx+ i 1)) t ...) cmd*)] [nargs (bump nargs n)]) (fx+ i 1)))))]))) (define-syntax parse-radix (syntax-rules () [(_ base) (directive (write-radix [implicit base] [nnfixnum? w #f] [char? pad-char #\space] [char? comma-char #\,] [pfixnum? comma-interval 3] [implicit at?] [implicit colon?]) 1)])) (state-case c [eof (incomplete-format-directive b i)] [(#\% #\n #\N) (no-at) (no-colon) (if (or (null? p*) (equal? p* '(1))) (state s0 ([cmd* (cons #\newline cmd*)]) (fx+ i 1)) (directive (dup-char [nnfixnum? n 1] [implicit #\newline]) 0))] [(#\&) (no-at) (no-colon) (directive (fresh-line [nnfixnum? n 1]) 0)] [(#\a #\A) (no-colon) (if (null? p*) (directive (simple-display) 1) (directive (display [nnfixnum? mincol 0] [pfixnum? colinc 1] [nnfixnum? minpad 0] [char? pad-char #\space] [implicit at?]) 1))] [(#\s #\S #\w #\W) (if (and (null? p*) (not colon?)) (directive (simple-write) 1) (directive (write [nnfixnum? mincol 0] [pfixnum? colinc 1] [nnfixnum? minpad 0] [char? pad-char #\space] [implicit colon?] [implicit at?]) 1))] [(#\f #\F) (no-colon) (directive (fwrite [nnfixnum? w #f] [nnfixnum? d #f] [fixnum? k 0] [char? overflow-char #f] [char? pad-char #\space] [implicit at?]) 1)] [(#\e #\E) (no-colon) (directive (ewrite [nnfixnum? w #f] [nnfixnum? d #f] [pfixnum? e #f] [fixnum? k 1] [char? overflow-char #f] [char? pad-char #\space] [char? exponent-char #\e] [implicit at?]) 1)] [(#\g #\G) (no-colon) (directive (gwrite [nnfixnum? w #f] [nnfixnum? d #f] [pfixnum? e #f] [fixnum? k 1] ; assumption [char? overflow-char #f] [char? pad-char #\space] [char? exponent-char #\e] [implicit at?]) 1)] [(#\$) (directive ($write [nnfixnum? d 2] [nnfixnum? n 1] [nnfixnum? w 0] [char? pad-char #\space] [implicit colon?] [implicit at?]) 1)] [(#\c #\C) (directive (cwrite [implicit colon?] [implicit at?]) 1)] [(#\b #\B) (parse-radix 2)] [(#\o #\O) (parse-radix 8)] [(#\d #\D) (parse-radix 10)] [(#\x #\X) (parse-radix 16)] [(#\r #\R) (if (null? p*) (directive (fancy-radix [implicit colon?] [implicit at?]) 1) (directive (write-radix [radix? n 10] [nnfixnum? w #f] [char? pad-char #\space] [char? comma-char #\,] [pfixnum? comma-interval 3] [implicit at?] [implicit colon?]) 1))] [(#\p #\P) (directive (plural [implicit colon?] [implicit at?]) (if colon? 0 1))] [(#\t #\T) (no-colon) (set! column? #t) (directive (tabulate [nnfixnum? colnum 1] [nnfixnum? colinc 1] [implicit at?]) 0)] [(#\?) (no-colon) (set! column? #t) (directive (indirect [implicit at?]) (if at? #f 2))] [(#\*) (when (and colon? at?) ($oops who "@ and : modifiers are mutually exclusive for format directive ~~~c" c)) (directive (goto [nnfixnum? n #f] [implicit colon?] [implicit at?]) #f)] [(#\( #|)|#) (parameters () (state s0 ([stack (cons (make-cvtcase-frame (substring cntl b (fx+ i 1)) cmd* colon? at?) stack)] [cmd* '()]) (fx+ i 1)))] [(#|(|# #\)) (no-at) (no-colon) (let ([x (and (not (null? stack)) (car stack))]) (unless (cvtcase-frame? x) (misplaced-directive)) (let ([nested-cmd* (reverse cmd*)]) (let ([stack (cdr stack)] [cmd* (frame-cmd* x)]) (directive (convert-case [implicit nested-cmd*] [implicit (cvtcase-frame-colon? x)] [implicit (cvtcase-frame-at? x)]) 0))))] [(#\;) (no-at) (let ([x (and (not (null? stack)) (car stack))]) (cond [(and (conditional/colon-frame? x) (not colon?) (not (conditional/colon-frame-altern x))) (parameters () (conditional/colon-frame-altern-set! x (reverse cmd*))) (state s0 ([cmd* '()]) (fx+ i 1))] [(and (conditional-frame? x) (not (conditional-frame-default? x))) (parameters () (when colon? (conditional-frame-default?-set! x #t)) (conditional-frame-cases-set! x (cons (reverse cmd*) (conditional-frame-cases x)))) (state s0 ([cmd* '()]) (fx+ i 1))] [(and (justify-frame? x) (or (not colon?) (and (not (justify-frame-initial x)) (null? (justify-frame-segments x))))) (if colon? (parameters ([margin (nnfixnum? n 0)] [cols (nnfixnum? lw 72)]) (set! column? #t) (justify-frame-initial-set! x (reverse cmd*)) (justify-frame-margin-set! x margin) (justify-frame-columns-set! x cols)) (parameters () (justify-frame-segments-set! x (cons (reverse cmd*) (justify-frame-segments x))))) (state s0 ([cmd* '()]) (fx+ i 1))] [else (misplaced-directive)]))] [(#\^) (no-at) (directive (abort [true? n #f] [true? m #f] [implicit colon?]) #f)] [(#\{ #|}|#) (when (null? cmd*) (set! column? #t)) (parameters ([n (nnfixnum? n #f)]) (state s0 ([stack (cons (make-iteration-frame (substring cntl b (fx+ i 1)) cmd* n colon? at?) stack)] [cmd* '()]) (fx+ i 1)))] [(#|{|# #\}) (no-at) (let ([x (and (not (null? stack)) (car stack))]) (unless (iteration-frame? x) (misplaced-directive)) (let ([nested-cmd* (reverse cmd*)]) (let ([stack (cdr stack)] [cmd* (frame-cmd* x)]) (directive (iteration [implicit nested-cmd*] [implicit (iteration-frame-n x)] [implicit (iteration-frame-sublists? x)] [implicit (iteration-frame-use-remaining? x)] [implicit colon?]) #f))))] [(#\[ #|]|#) (if at? (if colon? ($oops who "@ and : modifiers are mutually exclusive for format directive ~~~c" c) (parameters () (state s0 ([stack (cons (make-conditional/at-frame (substring cntl b (fx+ i 1)) cmd*) stack)] [cmd* '()]) (fx+ i 1)))) (if colon? (parameters () (state s0 ([stack (cons (make-conditional/colon-frame (substring cntl b (fx+ i 1)) cmd*) stack)] [cmd* '()]) (fx+ i 1))) (parameters ([n (nnfixnum? n #f)]) (state s0 ([stack (cons (make-conditional-frame (substring cntl b (fx+ i 1)) cmd* n) stack)] [cmd* '()]) (fx+ i 1)))))] [(#|[|# #\]) (no-at) (no-colon) (let ([x (and (not (null? stack)) (car stack))]) (let ([nested-cmd* (reverse cmd*)]) (cond [(conditional/at-frame? x) (let ([stack (cdr stack)] [cmd* (frame-cmd* x)]) (directive (conditional/at [implicit nested-cmd*]) #f))] [(conditional/colon-frame? x) (let ([altern (conditional/colon-frame-altern x)]) (unless altern ($oops who "no ~~; found within ~a...~~]" (frame-directive (car stack)))) (let ([stack (cdr stack)] [cmd* (frame-cmd* x)]) (directive (conditional/colon [implicit altern] [implicit nested-cmd*]) #f)))] [(conditional-frame? x) (let ([stack (cdr stack)] [cmd* (frame-cmd* x)]) (let ([n (conditional-frame-n x)]) (if (conditional-frame-default? x) (directive (conditional [implicit n] [implicit (list->vector (reverse (conditional-frame-cases x)))] [implicit nested-cmd*]) #f) (directive (conditional [implicit n] [implicit (list->vector (reverse (cons nested-cmd* (conditional-frame-cases x))))] [implicit '()]) #f))))] [else (misplaced-directive)])))] [(#\<) (parameters ([mincol (nnfixnum? mincol 0)] [colinc (nnfixnum? colinc 1)] [minpad (nnfixnum? minpad 0)] [pc (char? pad-char #\space)]) (state s0 ([stack (cons (make-justify-frame (substring cntl b (fx+ i 1)) cmd* mincol colinc minpad pc colon? at?) stack)] [cmd* '()]) (fx+ i 1)))] [(#\>) (no-at) (let ([x (and (not (null? stack)) (car stack))]) (unless (justify-frame? x) (misplaced-directive)) (let ([nested-cmd* (reverse cmd*)]) (let ([stack (cdr stack)] [cmd* (frame-cmd* x)]) (directive (justify [implicit (justify-frame-mincol x)] [implicit (justify-frame-colinc x)] [implicit (justify-frame-minpad x)] [implicit (justify-frame-pc x)] [implicit (justify-frame-before? x)] [implicit (justify-frame-after? x)] [implicit (justify-frame-initial x)] [implicit (justify-frame-margin x)] [implicit (justify-frame-columns x)] [implicit (reverse (cons nested-cmd* (justify-frame-segments x)))]) 0))))] [(#\~) (no-at) (no-colon) (if (or (null? p*) (equal? p* '(1))) (state s0 ([cmd* (cons #\~ cmd*)]) (fx+ i 1)) (directive (dup-char [nnfixnum? n 1] [implicit #\~]) 0))] [(#\|) (no-at) (no-colon) (if (or (null? p*) (equal? p* '(1))) (state s0 ([cmd* (cons #\page cmd*)]) (fx+ i 1)) (directive (dup-char [nnfixnum? n 1] [implicit #\page]) 0))] [(#\return) ; ~\r\n is treated like ~\n (if (eq? (char (fx+ i 1)) #\newline) (state s6 () (fx+ i 1) b p* colon? at?) ($oops who "unrecognized directive ~~~:c" c))] [(#\newline) (parameters () (when (and colon? at?) ($oops who "@ and : modifiers are mutually exclusive for format directive ~~~c" c)) (cond [colon? (state s0 () (fx+ i 1))] [at? (state s0 ([cmd* (cons c cmd*)]) (skip-non-newline-white (fx+ i 1)))] [else (state s0 () (skip-non-newline-white (fx+ i 1)))]))] [else ($oops who "unrecognized directive ~~~:c" c)])))) (state s0 ([nargs 0] [cmd* '()] [stack '()]) 0)))) ;;; squash together adjacent strings and characters (define squash (lambda (ls) (define insert-string! (lambda (s1 i1 s2) (let ([n2 (string-length s2)]) (do ([i1 i1 (fx+ i1 1)] [i2 0 (fx+ i2 1)]) ((fx= i2 n2)) (string-set! s1 i1 (string-ref s2 i2)))))) (define squash0 (lambda (ls) (let ([a (car ls)] [d (cdr ls)]) (if (null? d) ls (if (string? a) (let-values ([(s d) (squash1 d (string-length a))]) (if (string? s) (begin (insert-string! s 0 a) (cons s d)) (cons a d))) (if (char? a) (let-values ([(s d) (squash1 d 1)]) (if (string? s) (begin (string-set! s 0 a) (cons s d)) (cons a d))) (cons a (squash0 d)))))))) (define squash1 (lambda (ls n) (if (null? ls) (values n ls) (let ([a (car ls)] [d (cdr ls)]) (if (string? a) (let-values ([(s d) (squash1 d (fx+ n (string-length a)))]) (let ([s (if (string? s) s (make-string s))]) (insert-string! s n a) (values s d))) (if (char? a) (let-values ([(s d) (squash1 d (fx+ n 1))]) (let ([s (if (string? s) s (make-string s))]) (string-set! s n a) (values s d))) (values n (if (null? d) ls (cons a (squash0 d)))))))))) (if (null? ls) '() (squash0 ls)))) ;;; convert simple formats to expressions. returns #f for other inputs. (define (make-fmt->expr build-quote build-seq build-primcall) (lambda (src sexpr cmd* op arg*) (define-syntax make-seq (syntax-rules () [(_ ?a ?d) (let ([d ?d]) (and d (let ([a ?a]) (if (null? d) a (build-seq a d)))))])) (define-syntax make-call (syntax-rules () [(_ src proc arg ...) (build-primcall src sexpr 'proc (list arg ...))])) (if (null? cmd*) (build-quote (void)) (let f ([cmd* cmd*] [arg* arg*] [src src]) (if (null? cmd*) '() (let ([cmd (car cmd*)] [cmd* (cdr cmd*)]) (cond [(string? cmd) (make-seq (make-call src display-string (build-quote cmd) op) (f cmd* arg* #f))] [(char? cmd) (make-seq (make-call src write-char (build-quote cmd) op) (f cmd* arg* #f))] [(fmt? cmd) (and (not (null? arg*)) (fmt-case cmd [simple-display () (make-seq (make-call src display (car arg*) op) (f cmd* (cdr arg*) #f))] [simple-write () (make-seq (make-call src write (car arg*) op) (f cmd* (cdr arg*) #f))] [cwrite (colon? at?) (and (not colon?) (not at?) (make-seq (make-call src write-char (car arg*) op) (f cmd* (cdr arg*) #f)))] [else #f]))] [else ($oops 'fmt->expr "internal error: ~s" cmd)]))))))) ;;; perform formatting operation from parsed string (cmd*) (define dofmt (lambda (who fmt-op cntl cmd* arg*) (define flonum->digits #%$flonum->digits) (define flonum-sign #%$flonum-sign) (define (exact-integer? x) (or (fixnum? x) (bignum? x))) (define float-base 10) ; hardcoding base 10 for now (define fd->string (lambda (ls d n sign?) (define flonum-digit->char (lambda (n) (string-ref "#00123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" (fx+ n 2)))) (let ([s (car ls)] [e (cadr ls)] [ls (cddr ls)]) (let ([op (open-output-string)]) (if (eqv? s -1) (write-char #\- op) (when sign? (write-char #\+ op))) (cond [(fx< e 0) (when (fx> n 0) (display (make-string n #\0) op)) (write-char #\. op) (if (and (not d) (fx= (car ls) -1)) ; some flavor of 0.0 (write-char #\0 op) (do ([e e (fx+ e 1)] [d d (and d (fx- d 1))]) ((or (fx>= e -1) (and d (fx= d 0))) (do ([ls ls (cdr ls)] [d d (and d (fx- d 1))]) ((if d (fx= d 0) (fx< (car ls) 0))) (write-char (flonum-digit->char (car ls)) op))) (write-char #\0 op)))] [(fx= (car ls) -1) ; some flavor of 0.0 (display (make-string (if (and (fx= n 0) (eqv? d 0)) 1 n) #\0) op) (write-char #\. op) (display (make-string (or d 1) #\0) op)] [else (let ([n (fx- n e 1)]) (when (fx> n 0) (display (make-string n #\0) op))) (write-char (flonum-digit->char (car ls)) op) (do ([ls (cdr ls) (cdr ls)] [e e (fx- e 1)]) ((fx= e 0) (write-char #\. op) (if (and (not d) (fx< (car ls) 0)) (write-char (flonum-digit->char (car ls)) op) (do ([ls ls (cdr ls)] [d d (and d (fx- d 1))]) ((if d (fx= d 0) (fx< (car ls) 0))) (write-char (flonum-digit->char (car ls)) op)))) (write-char (flonum-digit->char (car ls)) op))]) (get-output-string op))))) (define string-upcase! (lambda (s) (let ([n (string-length s)]) (do ([i 0 (fx+ i 1)]) ((fx= i n)) (string-set! s i (char-upcase (string-ref s i))))))) (define string-downcase! (lambda (s) (let ([n (string-length s)]) (do ([i 0 (fx+ i 1)]) ((fx= i n)) (string-set! s i (char-downcase (string-ref s i))))))) (define string-capitalize! (lambda (s) (let ([n (string-length s)]) (define interword (lambda (i) (unless (fx= i n) (let ([c (string-ref s i)]) (if (or (char-alphabetic? c) (char-numeric? c)) (begin (string-set! s i (char-upcase c)) (intraword (fx+ i 1))) (interword (fx+ i 1))))))) (define intraword (lambda (i) (unless (fx= i n) (let ([c (string-ref s i)]) (if (or (char-alphabetic? c) (char-numeric? c)) (begin (string-set! s i (char-downcase c)) (intraword (fx+ i 1))) (interword (fx+ i 1))))))) (interword 0)))) (define string-capitalize-first! (lambda (s) (let ([n (string-length s)]) (unless (fx= (string-length s) 0) (string-set! s 0 (char-upcase (string-ref s 0))) (do ([i 1 (fx+ i 1)]) ((fx= i n)) (string-set! s i (char-downcase (string-ref s i)))))))) (define-syntax pad (syntax-rules () [(_ mincol colinc minpad pad-char left? op expr) (if (and (fx= mincol 0) (fx= minpad 0)) expr (let ([s (let ([op (open-output-string)]) expr (get-output-string op))]) (unless left? (display s op)) (let ([n (let ([n (fxmax 0 (fx- mincol minpad (string-length s)))]) (fx+ minpad (fx* (fxquotient (fx+ n (fx- colinc 1)) colinc) colinc)))]) (unless (fx= n 0) (display (make-string n pad-char) op))) (when left? (display s op))))])) (define (padnum w oc pc op s) (if (not w) (display s op) (let ([n (string-length s)]) (cond [(fx> n w) (if oc (display (make-string w oc) op) (display s op))] [else (when (fx< n w) (display (make-string (fx- w n) pc) op)) (display s op)])))) (define (write-old-roman x op) (if (<= 1 x 4999) (let f ([x x] [a '(1000 . #\M)] [ls '((500 . #\D) (100 . #\C) (50 . #\L) (10 . #\X) (5 . #\V) (1 . #\I))]) (if (>= x (car a)) (begin (write-char (cdr a) op) (f (- x (car a)) a ls)) (unless (null? ls) (f x (car ls) (cdr ls))))) (fprintf op "~d" x))) (define (write-roman x op) (if (<= 1 x 3999) (let f ([x x] [a '(1000 . "M")] [ls '((900 . "CM") (500 . "D") (400 . "CD") (100 . "C") (90 . "XC") (50 . "L") (40 . "XL") (10 . "X") (9 . "IX") (5 . "V") (4 . "IV") (1 . "I"))]) (if (>= x (car a)) (begin (display (cdr a) op) (f (- x (car a)) a ls)) (unless (null? ls) (f x (car ls) (cdr ls))))) (fprintf op "~d" x))) (module (write-ordinal write-cardinal) (define (f100 x op) (cond [(>= x 100) (f10 (quotient x 100) op) (display " hundred" op) (let ([x (remainder x 100)]) (unless (= x 0) (display " " op) (f10 x op)))] [else (f10 x op)])) (define (f10 x op) (cond [(>= x 20) (display (vector-ref v20 (quotient x 10)) op) (let ([x (remainder x 10)]) (unless (= x 0) (display "-" op) (f10 x op)))] [else (display (vector-ref v0 x) op)])) (define (f1000000 x op) (cond [(>= x 1000000) (f100 (quotient x 1000000) op) (display " million" op) (let ([x (remainder x 1000000)]) (unless (= x 0) (display " " op) (f1000 x op)))] [else (f1000 x op)])) (define (f1000 x op) (cond [(<= 1100 x 1999) (f100 x op)] [(>= x 1000) (f100 (quotient x 1000) op) (display " thousand" op) (let ([x (remainder x 1000)]) (unless (= x 0) (display " " op) (f100 x op)))] [else (f100 x op)])) (define (*f1000000 x op) (cond [(>= x 1000000) (f100 (quotient x 1000000) op) (let ([x (remainder x 1000000)]) (if (= x 0) (display " millionth" op) (begin (display " million " op) (*f1000 x op))))] [else (*f1000 x op)])) (define (*f1000 x op) (cond [(<= 1100 x 1999) (*f100 x op)] [(>= x 1000) (f100 (quotient x 1000) op) (let ([x (remainder x 1000)]) (if (= x 0) (display " thousandth" op) (begin (display " thousand " op) (*f100 x op))))] [else (*f100 x op)])) (define (*f100 x op) (cond [(>= x 100) (f10 (quotient x 100) op) (let ([x (remainder x 100)]) (if (= x 0) (display " hundredth" op) (begin (display " hundred " op) (*f10 x op))))] [else (*f10 x op)])) (define (*f10 x op) (cond [(>= x 20) (let ([q (quotient x 10)] [x (remainder x 10)]) (if (= x 0) (display (vector-ref *v20 q) op) (begin (display (vector-ref v20 q) op) (display "-" op) (*f10 x op))))] [else (display (vector-ref *v0 x) op)])) (define v20 '#(#f #f twenty thirty forty fifty sixty seventy eighty ninety)) (define v0 '#(zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen)) (define *v20 '#(#f #f twentieth thirtieth fortieth fiftieth sixtieth seventieth eightieth ninetieth)) (define *v0 '#(zeroth first second third fourth fifth sixth seventh eighth ninth tenth eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth eighteenth nineteenth)) (define (write-ordinal x op) (if (<= -999999999 x +999999999) (if (< x 0) (begin (display "minus " op) (*f1000000 (- x) op)) (*f1000000 x op)) (fprintf op "~:d~a" x (let ([n (remainder (abs x) 100)]) (if (<= 11 n 19) "th" (case (remainder n 10) [(1) "st"] [(2) "nd"] [(3) "rd"] [else "th"])))))) (define (write-cardinal x op) (if (<= -999999999 x +999999999) (if (< x 0) (begin (display "minus " op) (f1000000 (- x) op)) (f1000000 x op)) (fprintf op "~:d" x)))) (define cheap-scale (lambda (ls k) `(,(car ls) ,(fx+ (cadr ls) k) ,@(cddr ls)))) (define (do-fwrite-d op x w d k oc pc sign? ls) (let ([ls (cheap-scale ls k)]) (padnum w oc pc op (fd->string ls d (if (and w (fx< (cadr ls) 0) (fx> (fx+ (if (or sign? (fx= (car ls) -1)) 3 2) d) w)) 0 1) sign?)))) (define (do-fwrite op x w d k oc pc sign?) (cond [d (do-fwrite-d op x w d k oc pc sign? (flonum->digits x float-base 'absolute (fx- (fx+ k d))))] [w (padnum w oc pc op (let ([ls (cheap-scale (flonum->digits x float-base 'normal 0) k)]) (let ([s (car ls)] [e (cadr ls)]) (if (fx< e 0) (let ([n (fx+ w e (if (or sign? (fx< s 0)) -1 0))]) (let f ([ds (cddr ls)] [i n]) (if (fx<= i 0) (let ([ls (cheap-scale (flonum->digits x float-base 'absolute (fx- (fx+ k (fxmax (fx- n e 1) 1)))) k)]) (if (fx= (caddr ls) -1) ; rounded to zero? (if (fx< s 0) (if (fx< w 4) "-.0" "-0.0") (if sign? (if (fx< w 4) "+.0" "+0.0") (if (fx< w 3) ".0" "0.0"))) (fd->string ls #f 0 sign?))) (if (fx= (cadr ds) -1) ; can't be -2 w/normal (fd->string ls #f (if (fx= i 1) 0 1) sign?) (f (cdr ds) (fx- i 1)))))) (let ([n (fx+ w (if (or sign? (fx< s 0)) -2 -1))]) (let g ([e e] [ds (cddr ls)] [i n]) (if (fx< i 0) (if (fx< e -1) (fd->string (cheap-scale (flonum->digits x float-base 'absolute (fx- (fx+ e 2) k)) k) (and (fx= e -2) 0) 1 sign?) (fd->string (cheap-scale (flonum->digits x float-base 'absolute (fx- k)) k) 0 1 sign?)) (if (fx= (car ds) -1) ; can't be -2 w/normal (if (fx< e 0) (fd->string ls (and (fx= e -1) (fx= i 0) 0) 1 sign?) (if (fx< e (fx- i 1)) (fd->string (cheap-scale (flonum->digits x float-base 'absolute (fx- k (fx- i e))) k) #f 1 sign?) (fd->string (cheap-scale (flonum->digits x float-base 'absolute (fx- k)) k) 0 1 sign?))) (g (fx- e 1) (cdr ds) (fx- i 1))))))))))] [else (padnum w oc pc op (fd->string (let ([ls (cheap-scale (flonum->digits x float-base 'normal 0) k)]) (let f ([e (cadr ls)] [ds (cddr ls)]) (if (fx= (car ds) -1) ; w/normal, can't be -2 (cheap-scale (flonum->digits x float-base 'absolute (fx- -1 k)) k) (if (fx< e 0) ls (f (fx- e 1) (cdr ds)))))) d 1 sign?))])) (define (do-ewrite op x w d ew k oc pc ec sign?) (cond [(fl= x 0.0) (padnum w oc pc op (let ([ss (if (fx= (flonum-sign x) 1) "-" (if sign? "+" ""))] [es (if ew (make-string ew #\0) "0")]) (let ([d (and d (if (fx<= k 0) d (fx+ (fx- d k) 1)))]) (if (and w (fx> (fx+ (string-length ss) 4 (or d 1) (string-length es)) w)) (if (if d (fx= d 0) (fx> k 0)) (string-append ss "0." (string ec) "+" es) (string-append ss "." (if d (make-string d #\0) "0") (string ec) "+" es)) (string-append ss "0." (if d (make-string d #\0) "0") (string ec) "+" es)))))] [d (let ([ls (flonum->digits x float-base 'relative (if (fx<= k 0) (fx- (fx+ d k)) (fx- -1 d)))]) (let* ([e (fx- (cadr ls) (fx- k 1))] [es (number->string (fxabs e))] [esl (string-length es)]) (if (and w oc ew (fx> esl ew)) (display (make-string w oc) op) (let ([ew (if ew (fxmax ew esl) esl)]) (padnum w oc pc op (string-append (fd->string `(,(car ls) ,(fx- k 1) ,@(cddr ls)) (if (fx<= k 0) d (fx+ (fx- d k) 1)) (if (and w (fx> (fx+ (if (or sign? (fx= (car ls) -1)) 5 4) ew d) w)) 0 1) sign?) (if ec (string ec) "e") (if (fx< e 0) "-" "+") (make-string (fx- ew esl) #\0) es))))))] [w (let ([sign? (or sign? (fx= (flonum-sign x) 1))]) (let loop ([ew-guess (or ew 1)]) (let d ([d (fxmax (fx- w (if sign? 5 4) ew-guess) (if (fx= k 0) 0 (if (fx< k 0) (fx- 1 k) (fx- k 1))))]) (let ([ls (flonum->digits x float-base 'relative (if (fx<= k 0) (fx- (fx+ d k)) (fx- -1 d)))]) (let* ([e (fx- (cadr ls) (fx- k 1))] [es (number->string (fxabs e))] [esl (string-length es)]) (if (fx> esl ew-guess) (if (and oc ew) (display (make-string w oc) op) (loop esl)) (let ([ew (if ew (fxmax ew esl) esl)]) (padnum w oc pc op (string-append (fd->string `(,(car ls) ,(fx- k 1) ,@(cddr ls)) (and (fx= (fx- k d) 1) (fx>= (fx+ (if sign? 5 4) ew d) w) 0) (if (fx> (fx+ (if sign? 5 4) ew d) w) 0 1) sign?) (if ec (string ec) "e") (if (fx< e 0) "-" "+") (make-string (fx- ew esl) #\0) es)))))))))] [else (display (let ([ls (flonum->digits x float-base 'normal 0)]) (let ([e (fx- (cadr ls) (fx- k 1))]) (string-append (fd->string `(,(car ls) ,(fx- k 1) ,@(cddr ls)) #f 1 sign?) (if ec (string ec) "e") (if (fx< e 0) "-" "+") (let ([op (open-output-string)]) (padnum ew #f #\0 op (number->string (fxabs e))) (get-output-string op))))) op)])) (define invalid-parameter (lambda (who cmd what p) ($oops who "invalid ~s parameter ~a in directive ~s" what p (fmt-directive cmd)))) (define (outer-loop cmd* arg* op cntl all-arg* super-arg* ct? succ fail) (define tostr (lambda (cmd* arg* super-arg* succ fail) (let ([op (open-output-string)]) (let ([xop (if ct? (make-format-port op) op)]) (outer-loop cmd* arg* xop cntl all-arg* super-arg* ct? (lambda (arg*) (when ct? (close-output-port xop)) (succ (get-output-string op) arg*)) (lambda (arg* super?) (when ct? (close-output-port xop)) (fail (get-output-string op) arg* super?))))))) (define next (lambda (arg*) (when (null? arg*) ($oops who "too few arguments for control string ~s" cntl)) (car arg*))) (let loop ([cmd* cmd*] [arg* arg*]) (if (null? cmd*) (succ arg*) (let ([cmd (car cmd*)]) (define-syntax vparams (lambda (x) (define process-param (lambda (arg* t* param* body) (if (null? param*) body (with-syntax ([body (process-param arg* (cdr t*) (cdr param*) body)] [arg* arg*] [t (car t*)]) (syntax-case (car param*) () [(type? p) #'(let-values ([(t arg*) (cond [(eq? t 'v) (let ([t (next arg*)]) (unless (type? t) (invalid-parameter who cmd 'p t)) (values t (cdr arg*)))] [(eq? t 'hash) (let ([t (length arg*)]) (unless (type? t) (invalid-parameter who cmd 'p t)) (values t arg*))] [else (values t arg*)])]) body)]))))) (syntax-case x () [(_ arg* ([t param] ...) e1 e2 ...) (process-param #'arg* #'(t ...) #'(param ...) #'(let () e1 e2 ...))]))) (cond [(string? cmd) (display-string cmd op) (loop (cdr cmd*) arg*)] [(char? cmd) (write-char cmd op) (loop (cdr cmd*) arg*)] [(fmt? cmd) (fmt-case cmd [simple-display () (display (next arg*) op) (loop (cdr cmd*) (cdr arg*))] [simple-write () (write (next arg*) op) (loop (cdr cmd*) (cdr arg*))] [fresh-line (n) (vparams arg* ([n (nnfixnum? n)]) (when (fx> n 0) (fresh-line op) (when (fx> n 1) (display (make-string (fx- n 1) #\newline) op))) (loop (cdr cmd*) arg*))] [display (mincol colinc minpad pad-char left?) (vparams arg* ([mincol (nnfixnum? mincol)] [colinc (pfixnum? colinc)] [minpad (nnfixnum? minpad)] [pad-char (char? pad-char)]) (pad mincol colinc minpad pad-char left? op (display (next arg*) op)) (loop (cdr cmd*) (cdr arg*)))] [write (mincol colinc minpad pad-char nogensym? left?) (vparams arg* ([mincol (nnfixnum? mincol)] [colinc (pfixnum? colinc)] [minpad (nnfixnum? minpad)] [pad-char (char? pad-char)]) (pad mincol colinc minpad pad-char left? op (if nogensym? (parameterize ([print-gensym #f]) (write (next arg*) op)) (write (next arg*) op))) (loop (cdr cmd*) (cdr arg*)))] [cwrite (colon? at?) (let ([c (next arg*)]) (unless (char? c) ($oops who "expected character for ~~c, received ~s" c)) (if colon? (let ([x (char-name c)]) (if x (begin (write-char #\< op) (display x op) (write-char #\> op)) (let ([n (char->integer c)]) (if (fx< n #x20) (begin (write-char #\^ op) (write-char (integer->char (fx+ n #x40)) op)) (write-char c op))))) (if at? (write c op) (write-char c op)))) (loop (cdr cmd*) (cdr arg*))] [fwrite (w d k oc pc sign?) (vparams arg* ([w (nnfixnum? w)] [d (nnfixnum? d)] [k (fixnum? k)] [oc (char? overflow-char)] [pc (char? pad-char)]) (let ([x (next arg*)]) (unless (real? x) ($oops who "expected real number for ~~f, received ~s" x)) (let ([x (inexact x)]) (if (exceptional-flonum? x) (padnum w oc pc op (number->string x)) (do-fwrite op x w d k oc pc sign?)))) (loop (cdr cmd*) (cdr arg*)))] [ewrite (w d ew k oc pc ec sign?) (vparams arg* ([w (nnfixnum? w)] [d (nnfixnum? d)] [ew (nnfixnum? e)] [k (fixnum? k)] [oc (char? overflow-char)] [pc (char? pad-char)] [ec (char? exponent-char)]) (let ([x (next arg*)]) (unless (real? x) ($oops who "expected real number for ~~e, received ~s" x)) (let ([x (inexact x)]) (if (exceptional-flonum? x) (padnum w oc pc op (number->string x)) (if (or (not d) (fx< (fx- d) k (fx+ d 2))) (do-ewrite op x w d ew k oc pc ec sign?) ; signaling an error might be kind, but cltl2 says otherwise (if (and w oc) (display (make-string w oc) op) (let ([d (if (fx> k 0) (fx- k 1) (fx- 1 k))]) (do-ewrite op x w d ew k oc pc ec sign?))))))) (loop (cdr cmd*) (cdr arg*)))] [gwrite (w d ew k oc pc ec sign?) (vparams arg* ([w (nnfixnum? w)] [d (nnfixnum? d)] [ew (nnfixnum? e)] [k (fixnum? k)] [oc (char? overflow-char)] [pc (char? pad-char)] [ec (char? exponent-char)]) (let ([x (next arg*)]) #;(define (ilog x) (fx+ (cadr (flonum->digits x float-base 'normal 0)) 1)) (define (ilog x) ; 4x faster and good enough (if (fl= x 0.0) 0 (fx+ (flonum->fixnum (floor (fl- (fl* (log (flabs x)) (fl/ (log 10))) 1e-10))) 1))) (define significant-digits (lambda (ls) (if (fx< (car ls) 0) 0 (fx+ 1 (significant-digits (cdr ls)))))) (unless (real? x) ($oops who "expected real number for ~~g, received ~s" x)) (let ([x (inexact x)]) (if (exceptional-flonum? x) (padnum w oc pc op (number->string x)) (if d (let f ([n (ilog x)]) ; can x be negative here? (let ([dd (fx- d n)]) (if (not (fx<= 0 dd d)) (do-ewrite op x w d ew k oc pc ec sign?) (let ([ls (flonum->digits x float-base 'absolute (fx- dd))]) (let ([actual-n (fx+ (cadr ls) 1)]) (if (fx> actual-n n) ; e.g., .9999 came back as 1.000 (f actual-n) (let* ([ee (if ew (fx+ ew 2) 4)] [ww (and w (fx- w ee))]) ; scale k not used when treated as ~f (do-fwrite-d op x ww dd 0 oc pc sign? ls) (when w (display (make-string ee #\space) op))))))))) (let* ([ls (flonum->digits x float-base 'normal 0)] [n (fx+ (cadr ls) 1)] [est-d (max (significant-digits (cddr ls)) (min n 7))] [dd (fx- est-d n)]) (if (fx<= 0 dd est-d) (let* ([ee (if ew (fx+ ew 2) 4)] [ww (and w (fx- w ee))]) ; scale k not used when treated as ~f (do-fwrite op x ww dd 0 oc pc sign?) ; suppressing trailing whitespace when (not w) (when w (display (make-string ee #\space) op))) ; cltl seems to want our estimated d here (est-d) ; but original d (#f) makes more sense (do-ewrite op x w d ew k oc pc ec sign?))))))) (loop (cdr cmd*) (cdr arg*)))] [$write (d n w pc sign-before-pad? sign?) (vparams arg* ([d (nnfixnum? d)] [n (nnfixnum? n)] [w (nnfixnum? w)] [pc (char? pad-char)]) (let ([x (next arg*)]) (unless (real? x) ($oops who "expected real number for ~~$, received ~s" x)) (let ([x (inexact x)]) (if (exceptional-flonum? x) (padnum w #f pc op (number->string x)) (let ([ls (flonum->digits x float-base 'absolute (fx- d))]) (if (and sign-before-pad? (or sign? (fx= (car ls) -1))) (begin (write-char (if (fx= (car ls) -1) #\- #\+) op) (padnum (fx- w 1) #f pc op (fd->string (cons 1 (cdr ls)) d n #f))) (padnum w #f pc op (fd->string ls d n sign?))))))) (loop (cdr cmd*) (cdr arg*)))] [write-radix (base w pc cc ci sign? commas?) (vparams arg* ([base (radix? n)] [w (nnfixnum? w)] [pc (char? pad-char)] [cc (char? comma-char)] [ci (pfixnum? comma-interval)]) (let ([x (next arg*)]) (padnum w #f pc op (cond [(exact-integer? x) (let* ([s (number->string x base)] [s (if (and sign? (>= x 0)) (string-append "+" s) s)]) (if commas? (let* ([n (string-length s)] [sign (let ([c (string-ref s 0)]) (and (memv c '(#\+ #\-)) c))] [m (if sign (fx- n 1) n)] [nc (fxquotient (fx- m 1) ci)] [s2 (make-string (fx+ n nc))] [k (fxremainder m ci)] [k (if (fx= k 0) ci k)]) (define (loop i j k) (cond [(fx= i n) s2] [(fx= k 0) (string-set! s2 j cc) (loop i (fx+ j 1) ci)] [else (string-set! s2 j (string-ref s i)) (loop (fx+ i 1) (fx+ j 1) (fx- k 1))])) (cond [sign (string-set! s2 0 sign) (loop 1 1 k)] [else (loop 0 0 k)])) s))] [else (let ([op (open-output-string)]) (parameterize ([print-radix base]) (display x op)) (get-output-string op))]))) (loop (cdr cmd*) (cdr arg*)))] [plural (back-up? y/ies?) (let ([arg* (if back-up? (let f ([prev #f] [ls all-arg*]) (if (eq? ls arg*) (if prev prev ($oops who "no previous argument for ~a" (fmt-directive (car cmd*)))) (f ls (cdr ls)))) arg*)]) (if (eqv? (next arg*) 1) (when y/ies? (write-char #\y op)) (if y/ies? (display "ies" op) (write-char #\s op))) (loop (cdr cmd*) (cdr arg*)))] [fancy-radix (colon? at?) (let ([x (next arg*)]) (unless (exact-integer? x) ($oops who "expected exact integer for ~~r, received ~s" x)) (if colon? (if at? (write-old-roman x op) (write-ordinal x op)) (if at? (write-roman x op) (write-cardinal x op)))) (loop (cdr cmd*) (cdr arg*))] [dup-char (n c) (vparams arg* ([n (nnfixnum? n)]) (display (make-string n c) op) (loop (cdr cmd*) arg*))] [tabulate (colnum colinc relative?) (vparams arg* ([colnum (nnfixnum? colnum)] [colinc (nnfixnum? colinc)]) (cond [relative? (display (make-string colnum #\space) op) (unless (= colinc 0) (let ([col (output-column op)]) (when col (let ([n (modulo col colinc)]) (unless (= n 0) (display (make-string (- colinc n) #\space) op))))))] [else (let ([col (output-column op)]) (if col (if (>= col colnum) (unless (= colinc 0) (display (make-string (- colinc (modulo (- col colnum) colinc)) #\space) op)) (display (make-string (- colnum col) #\space) op)) (display " " op)))]) (loop (cdr cmd*) arg*))] [indirect (splice?) (let ([xcntl (next arg*)]) (unless (string? xcntl) ($oops who "first ~a argument ~s is not a string" (fmt-directive (car cmd*)) xcntl)) (let-values ([(xcmd* expected) (parse who xcntl)]) (if splice? (outer-loop xcmd* (cdr arg*) op cntl all-arg* #f ct? (lambda (arg*) (loop (cdr cmd*) arg*)) (lambda (arg* super?) (loop (cdr cmd*) arg*))) (let* ([arg* (cdr arg*)] [xarg* (next arg*)]) (let ([len ($list-length xarg* who)]) (when (and indirect-too-many-args-check expected) (check-nargs who expected len xcntl))) (outer-loop xcmd* xarg* op xcntl xarg* #f ct? (lambda (xarg*) (when (and dynamic-too-many-args-check (not (null? xarg*))) ($oops who "too many arguments for control string ~s" xcntl)) (loop (cdr cmd*) (cdr arg*))) (lambda (xarg* super?) (loop (cdr cmd*) (cdr arg*))))))))] [conditional (n cases default) (vparams arg* ([n (nnfixnum? n)]) (let-values ([(n arg*) (if n (values n arg*) (let ([n (next arg*)]) (values n (cdr arg*))))]) (loop (append (if (and (fixnum? n) (fx<= 0 n) (fx< n (vector-length cases))) (vector-ref cases n) default) (cdr cmd*)) arg*)))] [conditional/colon (alternative consequent) (let ([arg (next arg*)]) (loop (append (if arg consequent alternative) (cdr cmd*)) (cdr arg*)))] [conditional/at (consequent) (if (next arg*) (loop (append consequent (cdr cmd*)) arg*) (loop (cdr cmd*) (cdr arg*)))] [justify (mincol colinc minpad pc before? after? initial margin columns segments) (vparams arg* ([mincol (nnfixnum? mincol)] [colinc (nnfixnum? colinc)] [minpad (nnfixnum? minpad)] [pc (char? pad-char)]) (let () (define (process-segments initial complete segments arg*) (if (null? segments) (finalize initial (reverse complete) arg*) (tostr (car segments) arg* #f (lambda (s arg*) (process-segments initial (cons s complete) (cdr segments) arg*)) (lambda (s arg* super?) (finalize initial (reverse complete) arg*))))) (define (finalize initial segments arg*) (let* ([chars (apply fx+ (map string-length segments))] [segments (if before? (if after? `("" ,@segments "") `("" ,@segments)) (if after? `(,@segments "") (if (null? segments) '("") segments)))] [npads (fx- (length segments) 1)] [size (fx+ chars (fx* minpad npads))] [size (if (fx<= size mincol) mincol (fx+ size (fxmodulo (fx- mincol size) colinc)))]) (when initial (let ([oc (output-column op)]) (when (and oc (fx> (fx+ oc size margin) columns)) (display initial op)))) (cond [(fx= npads 0) ; right justify single item (display (make-string (fx- size chars) pc) op) (display (car segments) op)] [else (let* ([pad-amt (fx- size chars)] [pad-q (fxquotient pad-amt npads)] [pad-r (fxremainder pad-amt npads)] [pad-i (if (fx= pad-r 0) 0 (fxquotient npads pad-r))]) (let f ([s (car segments)] [s* (cdr segments)] [i 1] [pad-r pad-r]) (display s op) (unless (null? s*) (cond [(and (fx> pad-r 0) (fx= i 1)) (display (make-string (fx+ pad-q 1) pc) op) (f (car s*) (cdr s*) pad-i (fx- pad-r 1))] [else (display (make-string pad-q pc) op) (f (car s*) (cdr s*) (fx- i 1) pad-r)]))))])) (loop (cdr cmd*) arg*)) (if initial (tostr initial arg* #f (lambda (initial arg*) (process-segments initial '() segments arg*)) (lambda (s arg* super?) (finalize #f '() arg*))) (process-segments #f '() segments arg*))))] [goto (n reverse? absolute?) (vparams arg* ([n (nnfixnum? n)]) (loop (cdr cmd*) (cond [absolute? (let ([n (or n 0)]) (unless (fx<= n (length all-arg*)) ($oops who "~a would move beyond argument list" (fmt-directive (car cmd*)))) (list-tail all-arg* n))] [reverse? (let ([n (or n 1)]) (let ([n (fx- (length all-arg*) (length arg*) n)]) (unless (fx>= n 0) ($oops who "~a would move before first argument" (fmt-directive (car cmd*)))) (list-tail all-arg* n)))] [else (let ([n (or n 1)]) (unless (fx<= n (length arg*)) ($oops who "~a would move beyond argument list" (fmt-directive (car cmd*)))) (list-tail arg* n))])))] [convert-case (nested-cmd* colon? at?) (let () (define convert-display (lambda (s) (if colon? (if at? (string-upcase! s) (string-capitalize! s)) (if at? (string-capitalize-first! s) (string-downcase! s))) (display s op))) (tostr nested-cmd* arg* super-arg* (lambda (s arg*) (convert-display s) (loop (cdr cmd*) arg*)) (lambda (s arg* super?) (convert-display s) (fail arg* super?))))] [iteration (body n sublists? use-remaining? at-least-once?) (vparams arg* ([n (nnfixnum? n)]) (let-values ([(body body-cntl body-expected arg*) (if (null? body) (let ([arg (next arg*)]) (let-values ([(cmd* expected) (parse who arg)]) (values cmd* arg expected (cdr arg*)))) (values body cntl #f arg*))]) (if use-remaining? (if sublists? (let f ([n n] [arg* arg*] [at-least-once? at-least-once?]) (if (or (and n (fx= n 0)) (and (not at-least-once?) (null? arg*))) (loop (cdr cmd*) arg*) (let-values ([(xarg* arg*) (if (null? arg*) (values '() '()) (values (car arg*) (cdr arg*)))]) (let ([len ($list-length xarg* who)]) (when (and indirect-too-many-args-check body-expected) (check-nargs who body-expected len body-cntl))) (outer-loop body xarg* op body-cntl xarg* arg* ct? (lambda (xarg*) (when (and dynamic-too-many-args-check (not (null? xarg*))) ($oops who "too many arguments for control string ~s" body-cntl)) (f (and n (fx- n 1)) arg* #f)) (lambda (xarg* super?) (if super? (loop (cdr cmd*) arg*) (f (and n (fx- n 1)) arg* #f))))))) (let f ([n n] [arg* arg*] [at-least-once? at-least-once?]) (if (or (and n (fx= n 0)) (and (not at-least-once?) (null? arg*))) (loop (cdr cmd*) arg*) (outer-loop body arg* op body-cntl all-arg* #f ct? (lambda (arg*) (f (and n (fx- n 1)) arg* #f)) (lambda (arg* super?) (f (and n (fx- n 1)) arg* #f)))))) (let ([all-larg* (next arg*)]) (unless (list? all-larg*) ($oops who "~s is not a proper list" all-larg*)) (if sublists? (let f ([n n] [larg* all-larg*] [at-least-once? at-least-once?]) (if (or (and n (fx= n 0)) (and (not at-least-once?) (null? larg*))) (loop (cdr cmd*) (cdr arg*)) (let-values ([(xarg* larg*) (if (null? larg*) (values '() '()) (values (car larg*) (cdr larg*)))]) (let ([len ($list-length xarg* who)]) (when (and indirect-too-many-args-check body-expected) (check-nargs who body-expected len body-cntl))) (outer-loop body xarg* op body-cntl xarg* larg* ct? (lambda (xarg*) (when (and dynamic-too-many-args-check (not (null? xarg*))) ($oops who "too many arguments for control string ~s" body-cntl)) (f (and n (fx- n 1)) larg* #f)) (lambda (xarg* super?) (if super? (loop (cdr cmd*) (cdr arg*)) (f (and n (fx- n 1)) larg* #f))))))) (let f ([n n] [larg* all-larg*] [at-least-once? at-least-once?]) (if (or (and n (fx= n 0)) (and (not at-least-once?) (null? larg*))) (loop (cdr cmd*) (cdr arg*)) (outer-loop body larg* op body-cntl all-larg* #f ct? (lambda (larg*) (f (and n (fx- n 1)) larg* #f)) (lambda (larg* super?) (f (and n (fx- n 1)) larg* #f))))))))))] [abort (n m super?) (vparams arg* ([n (true? n)] [m (true? m)]) (if (if n (if m (eqv? n m) (eqv? n 0)) (null? (if super? super-arg* arg*))) (fail arg* super?) (loop (cdr cmd*) arg*)))] [columntrack (body) (let ([xop (make-format-port op)]) (outer-loop body arg* xop cntl arg* super-arg* #t (lambda (arg*) (close-output-port xop) (outer-loop (cdr cmd*) arg* op cntl arg* super-arg* ct? succ fail)) (lambda (arg* super?) (close-output-port xop) (fail arg* super?))))] [else ($oops who "internal error: ~s" cmd)])] [else ($oops who "internal error: ~s" cmd)]))))) (let ([op (or fmt-op (open-output-string))]) (outer-loop cmd* arg* op cntl arg* #f #f (lambda (arg*) (when (and dynamic-too-many-args-check (not (null? arg*))) ($oops who "too many arguments for control string ~s" cntl)) (void)) (lambda (arg* super?) (void))) (unless fmt-op (get-output-string op))))) (define check-nargs (lambda (who expected received cntl) (when (and expected received) (unless (fx= expected received) (if (fx< received expected) ($oops who "too few arguments for control string ~s" cntl) ($oops who "too many arguments for control string ~s" cntl)))))) (define format-port-name "format port") (define (output-column p) (unless (eq? (port-name p) format-port-name) ($oops 'format "internal error: port is not a format port")) ((port-handler p) 'column p)) (define make-format-port (lambda (subop) (define column 0) (define update-column! (lambda (p s n) (let f ([i 0] [col 0] [newline? #f]) (if (fx= i n) (begin (set! column (if newline? col (+ column col))) (set-port-bol! p newline?)) (if (char=? (string-ref s i) #\newline) (f (fx+ i 1) 0 #t) (f (fx+ i 1) (fx+ col 1) newline?)))))) (define handler (message-lambda (lambda (msg . args) ($oops 'format-port "operation ~s not handled" msg)) [(block-write p s n) (flush-output-port p) (update-column! p s n) (block-write subop s n)] [(clear-output-port p) (set-textual-port-output-index! p 0)] [(close-port p) (flush-output-port p) (set-textual-port-output-size! p 0) (mark-port-closed! p)] ; [(file-length p) #f] [(file-position p) (most-negative-fixnum)] [(file-position p pos) ($oops 'format-port "cannot reposition")] [(flush-output-port p) (let ([b (textual-port-output-buffer p)] [i (textual-port-output-index p)]) (unless (fx= i 0) (update-column! p b i) (block-write subop b i))) (set-textual-port-output-index! p 0)] [(port-name p) format-port-name] [(write-char c p) (let ([b (textual-port-output-buffer p)] [i (textual-port-output-index p)]) (string-set! b i c) (block-write subop b (fx+ i 1))) (set-textual-port-output-index! p 0)] [(column p) (flush-output-port p) column])) (let ([len 1024]) (let ([p (make-output-port handler (make-string len))]) (set-textual-port-output-size! p (fx- len 1)) (set-port-bol! p #t) p)))) (define go (lambda (who op cntl args) (let-values ([(cmd* expected) (parse who cntl)]) (when static-too-many-args-check (check-nargs who expected (length args) cntl)) (dofmt who op cntl cmd* args)))) (set! format (case-lambda [(port/cntl cntl/arg . args) (cond [(port? port/cntl) (unless (and (output-port? port/cntl) (textual-port? port/cntl)) ($oops 'format "~s is not a textual output port" port/cntl)) (go 'format port/cntl cntl/arg args)] [(eq? port/cntl #t) (go 'format (current-output-port) cntl/arg args)] [(eq? port/cntl #f) (go 'format #f cntl/arg args)] [else (go 'format #f port/cntl (cons cntl/arg args))])] [(cntl . args) (go 'format #f cntl args)])) (set! $dofmt dofmt) (set! $make-fmt->expr make-fmt->expr) (set! $parse-format-string (lambda (who cntl received) (let-values ([(cmd* expected) (parse who cntl)]) (when static-too-many-args-check (check-nargs who expected received cntl)) (squash cmd*)))) (set! printf (lambda (cntl . args) (go 'printf (current-output-port) cntl args))) (set! fprintf (lambda (op cntl . args) (unless (and (output-port? op) (textual-port? op)) ($oops 'fprintf "~s is not a textual output port" op)) (go 'fprintf op cntl args))))