add `racket/format'

The new library is Ryan's `unstable/cat', but the names have been
changed. (The task of removing `unstable/cat' remains.)
This commit is contained in:
Matthew Flatt 2012-09-06 10:30:04 -06:00
parent 5e5c564532
commit b53e458e3f
6 changed files with 1077 additions and 1 deletions

391
collects/racket/format.rkt Normal file
View File

@ -0,0 +1,391 @@
#lang racket/base
(require racket/contract/base
racket/list)
;; TO DO:
;; - avoid unnecessary intermediate strings
;; - see "Printing Floating-Point Numbers Quickly and Accurately"
;; by Berger & Dybvig, PLDI 1996 for ideas
;; MAYBE TO DO:
;; - recur into lists, vectors, etc?
;; - decimal separators (see "man 7 locale", lconv numeric fields)
;; - something for monetary amounts (see strfmon)?
;; (perhaps as separate library)
;; - prop:..., separate from prop:custom-write?
(define (non-empty-string? x)
(and (string? x) (positive? (string-length x))))
(define align-mode/c
(or/c 'left 'right 'center))
(define padding/c non-empty-string?)
(define sign-mode/c
(or/c #f '+ '++ 'parens
(let ([ind/c (or/c string? (list/c string? string?))])
(list/c ind/c ind/c ind/c))))
(define base/c
(or/c (integer-in 2 36)
(list/c 'up (integer-in 2 36))))
;; Precision is one of
;; - Nat, for "up to N"
;; - '(= N), for "exactly N"
(define precision/c
(or/c exact-nonnegative-integer?
(list/c '= exact-nonnegative-integer?)))
(define format/c
(->* ()
(#:separator string?
#:width (or/c exact-nonnegative-integer? #f)
#:max-width (or/c exact-nonnegative-integer? +inf.0)
#:min-width exact-nonnegative-integer?
#:limit-marker string?
#:align align-mode/c
#:pad-string padding/c
#:left-pad-string padding/c
#:right-pad-string padding/c)
#:rest list?
string?))
(provide/contract
[~a format/c]
[~s format/c]
[~v format/c]
[~e format/c]
[~.a format/c]
[~.s format/c]
[~.v format/c]
[~r (->* (rational?)
(#:exponential? any/c
#:sign sign-mode/c
#:base base/c
#:precision precision/c
#:format-exponent (or/c #f string? (-> exact-integer? string?))
#:min-width exact-positive-integer?
#:pad-string padding/c)
string?)])
;; ----------------------------------------
(define (%limit #:limit limit
#:limit-marker limit-marker
s)
(cond [(> (string-length s) limit)
(string-append (substring s 0 (- limit (string-length limit-marker)))
limit-marker)]
[else s]))
(define (%pad #:pad-to pad-to
#:align align-mode
#:left-padding left-padding
#:right-padding right-padding
s)
(cond [(< (string-length s) pad-to)
(let* ([s-length (string-length s)]
[to-pad-length (max 0 (- pad-to s-length))])
(let-values ([(left-pad-length right-pad-length)
(case align-mode
((left) (values 0 to-pad-length))
((right) (values to-pad-length 0))
((center)
(values (floor (/ to-pad-length 2))
(ceiling (/ to-pad-length 2)))))])
(string-append
(build-padding 'left left-padding left-pad-length)
s
(build-padding 'right right-padding right-pad-length))))]
[else s]))
(define (build-padding side padding pad-length)
(cond [(zero? pad-length) ""]
[(char? padding)
(make-string pad-length padding)]
[(and (string? padding) (= (string-length padding) 1))
(make-string pad-length (string-ref padding 0))]
[(string? padding)
(let* ([pattern padding]
[pattern-length (string-length pattern)]
[whole-copies (quotient pad-length pattern-length)]
[part-length (remainder pad-length pattern-length)]
[pattern-copies (for/list ([i (in-range whole-copies)]) pattern)])
(apply string-append
;; For left, start at start of string
;; For right, end at end of string.
(case side
((left)
(append pattern-copies
(list (substring pattern 0 part-length))))
((right)
(cons (substring pattern (- pattern-length part-length) pattern-length)
pattern-copies)))))]))
(define (do-checks who limit limit-marker width)
(when (> width limit)
(error who "pad-to length greater than limit (~s): ~s" limit width))
(when (> (string-length limit-marker) limit)
(error who "limit-marker string longer than limit (~s): ~e"
limit limit-marker)))
;; ----------------------------------------
(define ((%cat who fmt [default-sep " "] [default-limit-marker "..."])
#:width [width #f]
#:max-width [limit (or width +inf.0)]
#:limit-marker [limit-marker default-limit-marker]
#:min-width [pad-to (or width 0)]
#:align [align 'left]
#:pad-string [padding " "]
#:right-pad-string [right-padding padding]
#:left-pad-string [left-padding padding]
#:separator [sep default-sep]
. s)
(do-checks who limit limit-marker pad-to)
(%pad (%limit (if (and (pair? s) (null? (cdr s)))
(fmt (car s))
(apply string-append
(let ([s (map fmt s)])
(if (equal? sep "")
s
(add-between s sep)))))
#:limit limit
#:limit-marker limit-marker)
#:pad-to pad-to
#:align align
#:left-padding left-padding
#:right-padding right-padding))
(define ~a (%cat '~a (lambda (v) (if (string? v) v (format "~a" v))) "" ""))
(define ~s (%cat '~s (lambda (v) (format "~s" v))))
(define ~v (%cat '~v (lambda (v) (format "~v" v))))
(define ~e (%cat '~e (lambda (v) (format "~e" v))))
(define ~.a (%cat '~.a (lambda (v) (format "~.a" v)) "" ""))
(define ~.s (%cat '~.s (lambda (v) (format "~.s" v))))
(define ~.v (%cat '~.v (lambda (v) (format "~.v" v))))
;; ----
(define not-supplied (gensym))
(define (extract p) (if (list? p) (cadr p) p))
(define (~r N
#:sign [sign-mode #f]
#:base [base 10]
#:precision [precision 6]
#:exponential? [exponential? (not (or (zero? N)
(< (expt (extract base) (- (extract precision)))
(abs N)
(expt (extract base) (extract precision)))))]
#:format-exponent [exp-format-exponent #f]
#:min-width [pad-digits-to 1]
#:pad-string [digits-padding " "])
(if exponential?
(catne N
#:who 'catn
#:sign sign-mode
#:base base
#:precision precision
#:format-exponent exp-format-exponent
#:pad-digits-to pad-digits-to
#:digits-padding digits-padding)
(catnp N
#:who 'catn
#:sign sign-mode
#:base base
#:precision precision
#:pad-digits-to pad-digits-to
#:digits-padding digits-padding)))
(define (catnp N
#:who [who 'catnp]
#:sign [sign-mode #f]
#:base [base 10]
#:precision [precision 3]
#:pad-digits-to [pad-digits-to 1]
#:digits-padding [digits-padding " "])
;; precision: up to (or exactly) this many digits after decimal point
;; precision = 0 means no decimal point
;; precision = '(= 0) means keep decimal point
;; pad-digits-to: includes decimal point, doesn't include sign
(let*-values ([(upper? base) (normalize-base base)]
[(exactly? precision) (normalize-precision precision)])
(let* ([N-abs (abs N)]
[digits-part (%positional N-abs base upper? precision exactly?)]
[padded-digits-part
(%pad digits-part
#:pad-to pad-digits-to
#:align 'right
#:left-padding digits-padding
#:right-padding #f)])
(let-values ([(pre-sign-part post-sign-part) (get-sign-parts N sign-mode)])
(string-append pre-sign-part padded-digits-part post-sign-part)))))
(define (catne N
#:who [who 'catne]
#:sign [sign-mode #f]
#:base [base 10]
#:precision [precision 5]
#:format-exponent [format-exponent #f]
#:pad-digits-to [pad-digits-to 1]
#:digits-padding [digits-padding " "])
(let*-values ([(upper? base) (normalize-base base)]
[(exactly? precision) (normalize-precision precision)])
(let* ([N-abs (abs N)]
[digits-part
(%exponential N-abs base upper? format-exponent precision exactly?)]
[padded-digits-part
(%pad digits-part
#:pad-to pad-digits-to
#:align 'right
#:left-padding digits-padding
#:right-padding #f)])
(let-values ([(pre-sign-part post-sign-part) (get-sign-parts N sign-mode)])
(string-append pre-sign-part padded-digits-part post-sign-part)))))
(define (normalize-base base)
(if (pair? base)
(values (eq? (car base) 'up) (cadr base))
(values #f base)))
(define (normalize-precision precision)
(if (pair? precision)
(values #t (cadr precision))
(values #f precision)))
(define (%positional N-abs base upper? precision exactly?)
(let* ([Nw (inexact->exact (floor N-abs))]
[Nf (- N-abs Nw)]
[whole-part (number->string* Nw base upper?)]
[frac-part
(let* ([Nf* (inexact->exact (round (* Nf (expt base precision))))])
(cond [(and exactly? (= precision 0)) ""]
[exactly? (number->fraction-string Nf* base upper? precision)]
[(= Nf* 0) #f]
[else
(let-values ([(needed-precision Nf**)
(let loop ([np precision] [Nf* Nf*])
(let-values ([(q r) (quotient/remainder Nf* base)])
(cond [(zero? r) (loop (sub1 np) q)]
[else (values np Nf*)])))])
(number->fraction-string Nf** base upper? needed-precision))]))]
[digits-part
(cond [frac-part (string-append whole-part "." frac-part)]
[else whole-part])])
digits-part))
(define (%exponential N-abs base upper? format-exponent significand-precision exactly?)
(define-values (N* e-adjust actual-precision)
(scale N-abs base significand-precision exactly?))
;; hack: from 1234 want "1.234"; convert to "1234", mutate to ".234" after saving "1"
(let* ([digits (number->string* N* base upper?)]
[leading-digit (string (string-ref digits 0))]
[exponent (- significand-precision e-adjust)])
(string-set! digits 0 #\.)
(string-append leading-digit
(if (or exactly? (positive? actual-precision)) digits "")
(cond [(procedure? format-exponent)
(format-exponent exponent)]
[else
(string-append
(cond [(string? format-exponent) format-exponent]
[(= base 10) "e"]
[else (format "×~s^" base)])
(if (negative? exponent) "-" "+")
(%pad (number->string (abs exponent))
#:pad-to 2
#:align 'right
#:left-padding "0"
#:right-padding #f))]))))
(define (scale N-abs base significand-precision exactly?)
(if (zero? N-abs)
(values 0 0 (if exactly? significand-precision 0))
(scale/nz N-abs base significand-precision exactly?)))
(define (scale/nz N-abs base significand-precision exactly?)
(let* ([N (inexact->exact N-abs)]
[normalized-min (expt base significand-precision)]
[normalized-max (* base normalized-min)])
(let*-values ([(N*0 e-adjust0)
(let ([e-est (- significand-precision
(inexact->exact (floor (/ (log N-abs) (log base)))))])
(values (* N (expt base e-est)) e-est))]
[(N* e-adjust)
(let loop ([N N*0] [e e-adjust0] [r #f])
;; if r != #f, then N is integer
(cond [(< N normalized-min)
(loop (* N base) (add1 e) #f)]
[(>= N normalized-max)
(let-values ([(q r) (quotient/remainder (floor N) base)])
(loop q (sub1 e) r))]
[else
(let ([N* (if r
(if (>= (* 2 r) base) (add1 N) N)
(round* N))])
(cond [(< N* normalized-max)
(values N* e)]
[else (loop N* e #f)]))]))]
[(N* actual-precision)
(if exactly?
(values N* significand-precision)
(let loop ([N N*] [p significand-precision])
(let-values ([(q r) (quotient/remainder N base)])
(cond [(zero? r) (loop q (sub1 p))]
[else (values N p)]))))])
(values N* e-adjust actual-precision))))
;; ----
(define (get-sign-parts N sign-mode)
(define (get indicator)
(if (string? indicator)
(values indicator "")
(values (car indicator) (cadr indicator))))
(let ([indicator-table
(case sign-mode
((#f) '("" "" "-"))
((+) '("+" "" "-"))
((++) '("+" "+" "-"))
((parens) '("" "" ("(" ")")))
(else sign-mode))])
(cond [(or (negative? N) (eqv? -0.0 N))
(get (caddr indicator-table))]
[(zero? N)
(get (cadr indicator-table))]
[else ;; positive
(get (car indicator-table))])))
(define (number->string* N base upper?)
(cond [(memv base '(2 8 10 16))
(let ([s (number->string N base)])
(if (and (= base 16) upper?)
(string-upcase s)
s))]
[(zero? N)
(string #\0)]
[else
(apply string
(let loop ([N N] [digits null])
(cond [(zero? N) (reverse digits)]
[else (let-values ([(q r) (quotient/remainder N base)])
(loop q (cons (get-digit r upper?) digits)))])))]))
(define (number->fraction-string N base upper? precision)
(let ([s (number->string* N base upper?)])
(string-append (make-string (- precision (string-length s)) #\0) s)))
;; Allow base up to 36!
(define (get-digit d upper?)
(cond [(< d 10) (integer->char (+ d (char->integer #\0)))]
[else (integer->char (+ (- d 10) (char->integer (if upper? #\A #\a))))]))
(define (round* x) ;; round is round-to-even :(
(if (integer? x)
x
(+ (truncate x)
(if (even? (truncate (+ x x))) 0 1))))

View File

@ -30,6 +30,7 @@
racket/sequence
racket/local
racket/system
racket/format
(for-syntax racket/base))
(provide (all-from-out racket/contract
@ -61,5 +62,6 @@
racket/stream
racket/sequence
racket/local
racket/system)
racket/system
racket/format)
(for-syntax (all-from-out racket/base)))

View File

@ -0,0 +1,405 @@
#lang scribble/doc
@(require scribble/manual
scribble/struct
scribble/eval
"mz.rkt"
(for-label racket/contract
racket/math
racket/format))
@(begin
(define the-eval (make-base-eval))
(the-eval '(require racket/math racket/format)))
@title[#:tag "format"]{Converting Values to Strings}
@author[@author+email["Ryan Culpepper" "ryanc@racket-lang.org"]]
@note-lib[racket/format]
The @racketmodname[racket/format] library provides functions for
converting Racket values to strings. In addition to features like
padding and numeric formatting, the functions have the virtue of being
shorter than @racket[format] (with format string),
@racket[number->string], or @racket[string-append].
@defproc[(~a [v any/c] ...
[#:separator separator string? ""]
[#:width width (or/c exact-nonnegative-integer? #f) #f]
[#:max-width max-width (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
[#:min-width min-width exact-nonnegative-integer? (or width 0)]
[#:limit-marker limit-marker string? ""]
[#:align align (or/c 'left 'center 'right) 'left]
[#:pad-string pad-string non-empty-string? " "]
[#:left-pad-string left-pad-string non-empty-string? pad-string]
[#:right-pad-string right-pad-string non-empty-string? pad-string])
string?]{
Converts each @racket[v] to a string in @racket[display] mode---that
is, like @racket[(format "~a" v)]---then concatentates the results
with @racket[separator] between consecutive items, and then pads or
truncates the string to be at least @racket[min-width] characters and
at most @racket[max-width] characters.
@interaction[#:eval the-eval
(~a "north")
(~a 'south)
(~a #"east")
(~a #\w "e" 'st)
(~a (list "red" 'green #"blue"))
(~a 17)
(~a #e1e20)
(~a pi)
(~a (expt 6.1 87))
]
The @racket[~a] function is primarily useful for strings, numbers, and other
atomic data. The @racket[~v] and @racket[~s] functions are better suited to
compound data.
Let @racket[_s] be the concatenated string forms of the @racket[v]s
plus separators. If @racket[_s] is longer than @racket[max-width]
characters, it is truncated to exactly @racket[max-width]
characters. If @racket[_s] is shorter than @racket[min-width]
characters, it is padded to exactly @racket[min-width]
characters. Otherwise @racket[_s] is returned unchanged. If
@racket[min-width] is greater than @racket[max-width], an exception is
raised.
If @racket[_s] is longer than @racket[max-width] characters, it is
truncated and the end of the string is replaced with
@racket[limit-marker]. If @racket[limit-marker] is longer than
@racket[max-width], an exception is raised.
@interaction[#:eval the-eval
(~a "abcde" #:max-width 5)
(~a "abcde" #:max-width 4)
(~a "abcde" #:max-width 4 #:limit-marker "*")
(~a "abcde" #:max-width 4 #:limit-marker "...")
(~a "The quick brown fox" #:max-width 15 #:limit-marker "")
(~a "The quick brown fox" #:max-width 15 #:limit-marker "...")
]
If @racket[_s] is shorter than @racket[min-width], it is padded to at
least @racket[min-width] characters. If @racket[align] is
@racket['left], then only right padding is added; if @racket[align]
is @racket['right], then only left padding is added; and if
@racket[align] is @racket['center], then roughly equal amounts of
left padding and right padding are added.
Padding is specified as a non-empty string. Left padding consists of
@racket[left-pad-string] repeated in its entirety as many times as
possible followed by a @emph{prefix} of @racket[left-pad-string] to fill
the remaining space. In contrast, right padding consists of a
@emph{suffix} of @racket[right-pad-string] followed by a number of copies
of @racket[right-pad-string] in its entirety. Thus left padding starts
with the start of @racket[left-pad-string] and right padding ends with
the end of @racket[right-pad-string].
@interaction[#:eval the-eval
(~a "apple" #:min-width 20 #:align 'left)
(~a "pear" #:min-width 20 #:align 'left #:right-pad-string " .")
(~a "plum" #:min-width 20 #:align 'right #:left-pad-string ". ")
(~a "orange" #:min-width 20 #:align 'center
#:left-pad-string "- " #:right-pad-string " -")
]
Use @racket[width] to set both @racket[max-width] and @racket[min-width]
simultaneously, ensuring that the resulting string is exactly
@racket[width] characters long:
@interaction[#:eval the-eval
(~a "terse" #:width 6)
(~a "loquacious" #:width 6)
]
}
@;{----------------------------------------}
@defproc[(~v [v any/c] ...
[#:separator separator string? " "]
[#:width width (or/c exact-nonnegative-integer? #f) #f]
[#:max-width max-width (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
[#:min-width min-width exact-nonnegative-integer? (or width 0)]
[#:limit-marker limit-marker string? "..."]
[#:align align (or/c 'left 'center 'right) 'left]
[#:pad-string pad-string non-empty-string? " "]
[#:left-pad-string left-pad-string non-empty-string? pad-string]
[#:right-pad-string right-pad-string non-empty-string? pad-string])
string?]{
Like @racket[~a], but each value is converted like @racket[(format
"~v" v)], the default separator is @racket[" "], and the default limit
marker is @racket["..."].
@interaction[#:eval the-eval
(~v "north")
(~v 'south)
(~v #"east")
(~v #\w)
(~v (list "red" 'green #"blue"))
]
Use @racket[~v] to produce text that talks about Racket values.
@interaction[#:eval the-eval
(let ([nums (for/list ([i 10]) i)])
(~a "The even numbers in " (~v nums)
" are " (~v (filter even? nums)) "."))
]}
@;{----------------------------------------}
@defproc[(~s [v any/c] ...
[#:separator separator string? " "]
[#:width width (or/c exact-nonnegative-integer? #f) #f]
[#:max-width max-width (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
[#:min-width min-width exact-nonnegative-integer? (or width 0)]
[#:limit-marker limit-marker string? "..."]
[#:align align (or/c 'left 'center 'right) 'left]
[#:pad-string pad-string non-empty-string? " "]
[#:left-pad-string left-pad-string non-empty-string? pad-string]
[#:right-pad-string right-pad-string non-empty-string? pad-string])
string?]{
Like @racket[~a], but each value is converted like @racket[(format
"~s" v)], the default separator is @racket[" "], and the default limit
marker is @racket["..."].
@interaction[#:eval the-eval
(~s "north")
(~s 'south)
(~s #"east")
(~s #\w)
(~s (list "red" 'green #"blue"))
]
}
@;{----------------------------------------}
@defproc[(~r [x rational?]
[#:sign sign
(or/c #f '+ '++ 'parens
(let ([ind (or/c string? (list/c string? string?))])
(list/c ind ind ind)))
#f]
[#:base base
(or/c (integer-in 2 36) (list/c 'up (integer-in 2 36)))
10]
[#:precision precision
(or/c exact-nonnegative-integer?
(list/c '= exact-nonnegative-integer?))
3]
[#:exponential? exponential
any/c
(let ([num (lambda (n) (if (list? n) (cadr n) n))])
(not (or (zero? x)
(< (expt (num base) (- (num precision)))
(abs x)
(expt (num base) (num precision))))))]
[#:exp-precision exp-precision
(or/c exact-nonnegative-integer?
(list/c '= exact-nonnegative-integer?))
5]
[#:format-exponent format-exponent
(or/c #f string? (-> exact-integer? string?))
#f]
[#:min-width min-width exact-positive-integer? 1]
[#:pad-string pad-string non-empty-string? " "])
string?]{
Converts the rational number @racket[x] to a string in either
positional or exponential notation, depending on
@racket[exponential?]. The exactness or inexactness of @racket[x] does
not affect its formatting.
The optional arguments control number formatting:
@itemize[
@item{@racket[precision] --- controls the number of digits after the decimal point
(or more accurately, the
@hyperlink["http://en.wikipedia.org/wiki/Radix_point"]{radix point}). When @racket[x]
is converted to exponential form, @racket[precision] applies only to the significand.
If @racket[precision] is a natural number, then up to @racket[precision] digits are
displayed, but trailing zeroes are dropped, and if all digits after the decimal
point are dropped the decimal point is also dropped. If @racket[precision] is
@racket[(list '= _digits)], then exactly @racket[_digits] digits after the
decimal point are used, and the decimal point is never dropped.
@interaction[#:eval the-eval
(~r pi)
(~r pi #:precision 4)
(~r pi #:precision 0)
(~r 1.5 #:precision 4)
(~r 1.5 #:precision '(= 4))
(~r 50 #:precision 2)
(~r 50 #:precision '(= 2))
(~r 50 #:precision '(= 0))
]}
@item{@racket[min-width] --- if @racket[x] would normally be printed
with fewer than @racket[min-width] digits (including the decimal
point but not including the sign indicator), the output is left-padded
using @racket[pad-string].
@interaction[#:eval the-eval
(~r 17)
(~r 17 #:min-width 4)
(~r -42 #:min-width 4)
(~r 1.5 #:min-width 4)
(~r 1.5 #:precision 4 #:min-width 10)
(~r 1.5 #:precision '(= 4) #:min-width 10)
(~r 1e10 #:min-width 6)
]}
@item{@racket[pad-string] --- specifies the string used to pad the
number to at least @racket[min-width] characters (not including the
sign indicator). The padding is placed between the sign and the normal
digits of @racket[x].
@interaction[#:eval the-eval
(~r 17 #:min-width 4 #:pad-string "0")
(~r -42 #:min-width 4 #:pad-string "0")
]}
@item{@racket[sign] --- controls how the sign of the number is
indicated:
@itemlist[
@item{If @racket[sign] is @racket[#f] (the default), no sign output is
generated if @racket[x] is either positive or zero, and a minus sign is
prefixed if @racket[x] is negative.
@interaction[#:eval the-eval
(for/list ([x '(17 0 -42)]) (~r x))
]}
@item{If @racket[sign] is @racket['+], no sign output is generated if
@racket[x] is zero, a plus sign is prefixed if @racket[x] is positive, and a
minus sign is prefixed if @racket[x] is negative.
@interaction[#:eval the-eval
(for/list ([x '(17 0 -42)]) (~r x #:sign '+))
]}
@item{If @racket[sign] is @racket['++], a plus sign is prefixed if @racket[x]
is zero or positive, and a minus sign is prefixed if @racket[x] is negative.
@interaction[#:eval the-eval
(for/list ([x '(17 0 -42)]) (~r x #:sign '++))
]}
@item{If @racket[sign] is @racket['parens], no sign output is generated if
@racket[x] is zero or positive, and the number is enclosed in parentheses if
@racket[x] is negative.
@interaction[#:eval the-eval
(for/list ([x '(17 0 -42)]) (~r x #:sign 'parens))
]}
@item{If @racket[sign] is @racket[(list _pos-ind _zero-ind _neg-ind)], then
@racket[_pos-ind], @racket[_zero-ind], and @racket[_neg-ind] are used to
indicate positive, zero, and negative numbers, respectively. Each indicator is
either a string to be used as a prefix or a list containing two strings: a
prefix and a suffix.
@interaction[#:eval the-eval
(let ([sign-table '(("" " up") "an even " ("" " down"))])
(for/list ([x '(17 0 -42)]) (~r x #:sign sign-table)))
]
The default behavior is equivalent to @racket['("" "" "-")]; the
@racket['parens] mode is equivalent to @racket['("" "" ("(" ")"))].
}
]}
@item{@racket[base] --- controls the base that @racket[x] is formatted in. If
@racket[base] is a number greater than @racket[10], then lower-case letters are
used. If @racket[base] is @racket[(list 'up _base*)] and @racket[_base*] is
greater than @racket[10], then upper-case letters are used.
@interaction[#:eval the-eval
(~r 100 #:base 7)
(~r 4.5 #:base 2)
(~r 3735928559 #:base 16)
(~r 3735928559 #:base '(up 16))
]}
@item{@racket[format-exponent] --- determines how the exponent is displayed.
If @racket[format-exponent] is a string, the exponent is displayed with an
explicit sign (as with a @racket[sign-mode] of @racket['++]) and at least two
digits, separated from the significand by the ``exponent marker''
@racket[format-exponent]:
@interaction[#:eval the-eval
(~r 1234 #:exponential? #t #:format-exponent "E")
]
If @racket[format-exponent] is @racket[#f], the ``exponent marker'' is
@racket["e"] if @racket[base] is @racket[10] and a string involving
@racket[base] otherwise:
@interaction[#:eval the-eval
(~r 1234 #:exponential? #t)
(~r 1234 #:exponential? #t #:base 8)
]
If @racket[format-exponent] is a procedure, it is applied to the exponent and
the resulting string is appended to the significand:
@interaction[#:eval the-eval
(~r 1234 #:exponential? #t
#:format-exponent (lambda (e) (format "E~a" e)))
]}
]
}
@; ----------------------------------------
@deftogether[(
@defproc[(~.a [v any/c] ...
[#:separator separator string? ""]
[#:width width (or/c exact-nonnegative-integer? #f) #f]
[#:max-width max-width (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
[#:min-width min-width exact-nonnegative-integer? (or width 0)]
[#:limit-marker limit-marker string? ""]
[#:align align (or/c 'left 'center 'right) 'left]
[#:pad-string pad-string non-empty-string? " "]
[#:left-pad-string left-pad-string non-empty-string? pad-string]
[#:right-pad-string right-pad-string non-empty-string? pad-string])
string?]
@defproc[(~.v [v any/c] ...
[#:separator separator string? " "]
[#:width width (or/c exact-nonnegative-integer? #f) #f]
[#:max-width max-width (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
[#:min-width min-width exact-nonnegative-integer? (or width 0)]
[#:limit-marker limit-marker string? "..."]
[#:align align (or/c 'left 'center 'right) 'left]
[#:pad-string pad-string non-empty-string? " "]
[#:left-pad-string left-pad-string non-empty-string? pad-string]
[#:right-pad-string right-pad-string non-empty-string? pad-string])
string?]
@defproc[(~.s [v any/c] ...
[#:separator separator string? " "]
[#:width width (or/c exact-nonnegative-integer? #f) #f]
[#:max-width max-width (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
[#:min-width min-width exact-nonnegative-integer? (or width 0)]
[#:limit-marker limit-marker string? "..."]
[#:align align (or/c 'left 'center 'right) 'left]
[#:pad-string pad-string non-empty-string? " "]
[#:left-pad-string left-pad-string non-empty-string? pad-string]
[#:right-pad-string right-pad-string non-empty-string? pad-string])
string?]
)]{
Like @racket[~a], @racket[~v], and @racket[~s], but each @racket[v] is
formatted like @racket[(format "~.a" v)], @racket[(format "~.v" v)],
and @racket[(format "~.s" v)], respectively.}
@; ----------------------------------------
@(close-eval the-eval)

View File

@ -500,5 +500,8 @@ trimmed (which is an alternative to using a @tech{regular expression}
(string-trim "aaaxaayaa" "aa")
]}
@; ----------------------------------------
@include-section["format.scrbl"]
@; ----------------------------------------
@close-eval[string-eval]

View File

@ -0,0 +1,274 @@
#lang racket/base
(require rackunit
racket/math
racket/format)
(define-syntax-rule (tc expr expected)
(test-equal? (format "~s" 'expr) expr expected))
(define-syntax-rule (tcrx expr len rx)
(test-case (format "~s" 'expr)
(let ([v expr])
(when len (check-equal? (string-length v) len))
(check-regexp-match rx v))))
;; ~a
(tc (~a "north")
"north")
(tc (~a 'south)
"south")
(tc (~a #"east")
"east")
(tc (~a #\w "e" 'st)
"west")
(tc (~a (list "red" 'green #"blue"))
"(red green blue)")
(tc (~a 17)
"17")
(tc (~a #e1e20)
(number->string #e1e20))
(tc (~a pi)
(number->string pi))
(tc (~a (expt 6.1 87))
(number->string (expt 6.1 87)))
(tc (~a "a" "b" "c" #:width 5)
"abc ")
(tc (~a "abcde" #:max-width 5)
"abcde")
(tc (~a "abcde" #:max-width 4)
"abcd")
(tc (~a "abcde" #:max-width 4 #:limit-marker "...")
"a...")
(tc (~a "abcde" #:max-width 4 #:limit-marker "*")
"abc*")
(tc (~a "abcde" #:max-width 4 #:limit-marker "")
"abcd")
(tc (~a "The quick brown fox" #:max-width 15 #:limit-marker "")
"The quick brown")
(tc (~a "The quick brown fox" #:max-width 15 #:limit-marker "...")
"The quick br...")
(tcrx (~a "apple" #:min-width 20 #:align 'left)
20 #rx"^apple( )*$")
(tcrx (~a "pear" #:min-width 20 #:align 'left #:right-pad-string " x")
20 #rx"^pear(x)?( x)*$")
(tcrx (~a "plum" #:min-width 20 #:align 'right #:left-pad-string "x ")
20 #rx"^(x )*(x)?plum$")
(tcrx (~a "orange" #:min-width 20 #:align 'center
#:left-pad-string "- " #:right-pad-string " -")
20 #rx"^(- )*(-)?orange(-)?( -)*$")
(tc (~a "short" #:width 6)
"short ")
(tc (~a "loquacious" #:width 6 #:limit-marker "...")
"loq...")
;; ~v
(tc (~v "north")
"\"north\"")
(tc (~v 'south)
"'south")
(tc (~v #"east")
"#\"east\"")
(tc (~v #\w)
"#\\w")
(tc (~v (list "red" 'green #"blue"))
"'(\"red\" green #\"blue\")")
(tc (~v '(123456) #:max-width 5)
"'(...")
;; ~s
(tc (~s "north")
"\"north\"")
(tc (~s 'south)
"south")
(tc (~s #"east")
"#\"east\"")
(tc (~s #\w)
"#\\w")
(tc (~s (list "red" 'green #"blue"))
"(\"red\" green #\"blue\")")
(tc (~s 123456 #:max-width 5)
"12...")
;; ~r
(tc (~r 0)
"0")
(tc (~r pi)
"3.141593")
(tc (~r pi #:precision 4)
"3.1416")
(tc (~r pi #:precision 0 #:exponential? #f)
"3")
(tc (~r 1.5 #:precision 4)
"1.5")
(tc (~r 1.5 #:precision '(= 4))
"1.5000")
(tc (~r 50 #:precision 2)
"50")
(tc (~r 50 #:precision '(= 2))
"50.00")
(tc (~r 50 #:precision '(= 0) #:exponential? #f)
"50.")
(tc (~r 17)
"17")
(tc (~r 17 #:min-width 4)
" 17")
(tc (~r -42 #:min-width 4)
"- 42")
(tc (~r 1.5 #:min-width 4)
" 1.5")
(tc (~r 1.5 #:precision 4 #:min-width 10)
" 1.5")
(tc (~r 1.5 #:precision '(= 4) #:min-width 10)
" 1.5000")
(tc (~r -42 #:min-width 4 #:pad-string "0")
"-0042")
(tc (~r 17 #:min-width 4 #:pad-string "0")
"0017")
(tc (~r -42 #:min-width 4 #:pad-string "0")
"-0042")
(tc (for/list ([x '(17 0 -42)]) (~r x))
'("17" "0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r x #:sign '+))
'("+17" "0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r x #:sign '++))
'("+17" "+0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r x #:sign 'parens))
'("17" "0" "(42)"))
(tc (let ([sign-table '(("" " up") "an even " ("" " down"))])
(for/list ([x '(17 0 -42)]) (~r x #:sign sign-table)))
'("17 up" "an even 0" "42 down"))
(tc (~r 100 #:base 7)
"202")
(tc (~r 4.5 #:base 2)
"100.1")
(tc (~r 3735928559 #:base 16 #:precision 10)
"deadbeef")
(tc (~r 3735928559 #:base '(up 16) #:precision 10)
"DEADBEEF")
(tc (~r 999 #:precision 3)
"999")
(tc (~r 1000 #:precision 3)
"1e+03")
(tc (~r 0.9876 #:exponential? #t #:precision 3)
"9.876e-01")
(tc (~r 100 #:base 2 #:precision 3)
"1.101×2^+06")
(tc (~r 1234 #:precision 3 #:format-exponent "E")
"1.234E+03")
(tc (~r 12345 #:precision 3)
"1.235e+04")
(tc (~r 12345 #:precision 2)
"1.23e+04")
(tc (~r 10000 #:precision 2)
"1e+04")
(tc (~r 10000 #:precision '(= 2))
"1.00e+04")
(tc (~r 12345 #:precision 4
#:min-width 12)
" 1.2345e+04")
;; ~r #:exponential? #f
(tc (~r #:exponential? #f pi)
"3.141593")
(tc (~r #:exponential? #f pi #:precision 4)
"3.1416")
(tc (~r #:exponential? #f pi #:precision 0)
"3")
(tc (~r #:exponential? #f 1.5 #:precision 4)
"1.5")
(tc (~r #:exponential? #f 1.5 #:precision '(= 4))
"1.5000")
(tc (~r #:exponential? #f 50 #:precision 2)
"50")
(tc (~r #:exponential? #f 50 #:precision '(= 2))
"50.00")
(tc (~r #:exponential? #f 50 #:precision '(= 0))
"50.")
(tc (~r #:exponential? #f 17)
"17")
(tc (~r #:exponential? #f 17 #:min-width 4)
" 17")
(tc (~r #:exponential? #f -42 #:min-width 4)
"- 42")
(tc (~r #:exponential? #f 1.5 #:min-width 4)
" 1.5")
(tc (~r #:exponential? #f 1.5 #:precision 4 #:min-width 10)
" 1.5")
(tc (~r #:exponential? #f 1.5 #:precision '(= 4) #:min-width 10)
" 1.5000")
(tc (~r #:exponential? #f -42 #:min-width 4 #:pad-string "0")
"-0042")
(tc (~r #:exponential? #f 17 #:min-width 4 #:pad-string "0")
"0017")
(tc (~r #:exponential? #f -42 #:min-width 4 #:pad-string "0")
"-0042")
(tc (for/list ([x '(17 0 -42)]) (~r #:exponential? #f x))
'("17" "0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r #:exponential? #f x #:sign '+))
'("+17" "0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r #:exponential? #f x #:sign '++))
'("+17" "+0" "-42"))
(tc (for/list ([x '(17 0 -42)]) (~r #:exponential? #f x #:sign 'parens))
'("17" "0" "(42)"))
(tc (let ([sign-table '(("" " up") "an even " ("" " down"))])
(for/list ([x '(17 0 -42)]) (~r #:exponential? #f x #:sign sign-table)))
'("17 up" "an even 0" "42 down"))
(tc (~r #:exponential? #f 100 #:base 7)
"202")
(tc (~r #:exponential? #f 4.5 #:base 2)
"100.1")
(tc (~r #:exponential? #f 3735928559 #:base 16)
"deadbeef")
(tc (~r #:exponential? #f 3735928559 #:base '(up 16))
"DEADBEEF")
;; ~r #:exponential? #t
(tc (~r #:exponential? #t 1000)
"1e+03")
(tc (~r #:exponential? #t 0.9876)
"9.876e-01")
(tc (~r #:exponential? #t 100 #:base 2)
"1.1001×2^+06")
(tc (~r #:exponential? #t 1234 #:format-exponent "E")
"1.234E+03")
(tc (~r #:exponential? #t 12345 #:precision 3)
"1.235e+04")
(tc (~r #:exponential? #t 12345 #:precision 2)
"1.23e+04")
(tc (~r #:exponential? #t 10000 #:precision 2)
"1e+04")
(tc (~r #:exponential? #t 10000 #:precision '(= 2))
"1.00e+04")
(tc (~r #:exponential? #t 12345 #:min-width 12)
" 1.2345e+04")

View File

@ -1,6 +1,7 @@
Version 5.3.0.22
Changed a thread's initial prompt to use the default handler
(instead of accepting and ignoring abort arguments)
Added racket/format, which is re-exported by racket
ffi/unsafe: added cpointer-gcable?
racket/class: added dynamic-get-field and dynamic-set-field!