473 lines
17 KiB
Racket
473 lines
17 KiB
Racket
#lang scribble/doc
|
|
@(require scribble/manual
|
|
scribble/struct
|
|
scribble/eval
|
|
(for-label racket/base
|
|
racket/contract
|
|
racket/math
|
|
unstable/cat
|
|
unstable/contract))
|
|
|
|
@(begin
|
|
(define the-eval (make-base-eval))
|
|
(the-eval '(require racket/math unstable/cat)))
|
|
|
|
@title[#:tag "cat"]{Converting Values to Strings}
|
|
@author[@author+email["Ryan Culpepper" "ryanc@racket-lang.org"]]
|
|
|
|
@defmodule[unstable/cat]
|
|
|
|
This module provides a few functions for converting Racket values to strings. In
|
|
addition to features like padding and numeric formatting, the functions have the
|
|
virtue of being shorter and more pleasant to type than @racket[format] (with
|
|
format string), @racket[number->string], or @racket[string-append].
|
|
|
|
@defproc[(cat [v any/c] ...
|
|
[#:width width (or/c exact-nonnegative-integer? #f) #f]
|
|
[#:limit limit (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
|
|
[#:limit-marker limit-marker string? "..."]
|
|
[#:pad-to pad-to exact-nonnegative-integer? (or width 0)]
|
|
[#:align align (or/c 'left 'center 'right) 'left]
|
|
[#:padding padding non-empty-string? " "]
|
|
[#:left-padding left-padding non-empty-string? padding]
|
|
[#:right-padding right-padding non-empty-string? padding])
|
|
string?]{
|
|
|
|
Converts each @racket[v] to a string in @racket[display] mode---that
|
|
is, like @racket[(format "~a" v)]---then concatentates the results and
|
|
pads or truncates the string to be at least @racket[pad-to] characters
|
|
and at most @racket[limit] characters.
|
|
|
|
@interaction[#:eval the-eval
|
|
(cat "north")
|
|
(cat 'south)
|
|
(cat #"east")
|
|
(cat #\w "e" 'st)
|
|
(cat (list "red" 'green #"blue"))
|
|
(cat 17)
|
|
(cat #e1e20)
|
|
(cat pi)
|
|
(cat (expt 6.1 87))
|
|
]
|
|
|
|
The @racket[cat] function is primarily useful for strings, numbers, and other
|
|
atomic data. The @racket[catp] and @racket[catw] functions are better suited to
|
|
compound data.
|
|
|
|
Let @racket[_s] be the concatenated string forms of the
|
|
@racket[v]s. If @racket[_s] is longer than @racket[limit] characters,
|
|
it is truncated to exactly @racket[limit] characters. If @racket[_s]
|
|
is shorter than @racket[pad-to] characters, it is padded to exactly
|
|
@racket[pad-to] characters. Otherwise @racket[_s] is returned
|
|
unchanged. If @racket[pad-to] is greater than @racket[limit], an
|
|
exception is raised.
|
|
|
|
If @racket[_s] is longer than @racket[limit] characters, it is truncated and the
|
|
end of the string is replaced with @racket[limit-marker]. If
|
|
@racket[limit-marker] is longer than @racket[limit], an exception is raised.
|
|
|
|
@interaction[#:eval the-eval
|
|
(cat "abcde" #:limit 5)
|
|
(cat "abcde" #:limit 4)
|
|
(cat "abcde" #:limit 4 #:limit-marker "*")
|
|
(cat "abcde" #:limit 4 #:limit-marker "")
|
|
(cat "The quick brown fox" #:limit 15 #:limit-marker "")
|
|
(cat "The quick brown fox" #:limit 15 #:limit-marker "...")
|
|
]
|
|
|
|
If @racket[_s] is shorter than @racket[pad-to], it is padded to at
|
|
least @racket[pad-to] 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-padding] repeated in its entirety as many times as
|
|
possible followed by a @emph{prefix} of @racket[left-padding] to fill
|
|
the remaining space. In contrast, right padding consists of a
|
|
@emph{suffix} of @racket[right-padding] followed by a number of copies
|
|
of @racket[right-padding] in its entirety. Thus left padding starts
|
|
with the start of @racket[left-padding] and right padding ends with
|
|
the end of @racket[right-padding].
|
|
|
|
@interaction[#:eval the-eval
|
|
(cat "apple" #:pad-to 20 #:align 'left)
|
|
(cat "pear" #:pad-to 20 #:align 'left #:right-padding " .")
|
|
(cat "plum" #:pad-to 20 #:align 'right #:left-padding ". ")
|
|
(cat "orange" #:pad-to 20 #:align 'center
|
|
#:left-padding "- " #:right-padding " -")
|
|
]
|
|
|
|
Use @racket[width] to set both @racket[limit] and @racket[pad-to]
|
|
simultaneously, ensuring that the resulting string is exactly
|
|
@racket[width] characters long:
|
|
|
|
@interaction[#:eval the-eval
|
|
(cat "terse" #:width 6)
|
|
(cat "loquacious" #:width 6)
|
|
]
|
|
}
|
|
|
|
@;{----------------------------------------}
|
|
|
|
@defproc[(catp [v any/c]
|
|
[#:width width (or/c exact-nonnegative-integer? #f) #f]
|
|
[#:limit limit (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
|
|
[#:limit-marker limit-marker string? "..."]
|
|
[#:pad-to pad-to exact-nonnegative-integer? (or width 0)]
|
|
[#:align align (or/c 'left 'center 'right) 'left]
|
|
[#:padding padding non-empty-string? " "]
|
|
[#:left-padding left-padding non-empty-string? padding]
|
|
[#:right-padding right-padding non-empty-string? padding])
|
|
string?]{
|
|
|
|
Like @racket[cat], but converts a single @racket[v] to a string in
|
|
@racket[print] mode---that is, like @racket[(format "~v" v)].
|
|
|
|
@interaction[#:eval the-eval
|
|
(catp "north")
|
|
(catp 'south)
|
|
(catp #"east")
|
|
(catp #\w)
|
|
(catp (list "red" 'green #"blue"))
|
|
]
|
|
|
|
Use @racket[catp] to produce text that talks about Racket values.
|
|
|
|
@interaction[#:eval the-eval
|
|
(let ([nums (for/list ([i 10]) i)])
|
|
(cat "The even numbers in " (catp nums)
|
|
" are " (catp (filter even? nums)) "."))
|
|
]
|
|
}
|
|
|
|
@;{----------------------------------------}
|
|
|
|
@defproc[(catw [v any/c]
|
|
[#:width width (or/c exact-nonnegative-integer? #f) #f]
|
|
[#:limit limit (or/c exact-nonnegative-integer? +inf.0) (or width +inf.0)]
|
|
[#:limit-marker limit-marker string? "..."]
|
|
[#:pad-to pad-to exact-nonnegative-integer? (or width 0)]
|
|
[#:align align (or/c 'left 'center 'right) 'left]
|
|
[#:padding padding non-empty-string? " "]
|
|
[#:left-padding left-padding non-empty-string? padding]
|
|
[#:right-padding right-padding non-empty-string? padding])
|
|
string?]{
|
|
|
|
Like @racket[cat], but converts a single @racket[v] to a string in
|
|
@racket[write] mode---that is, like @racket[(format "~s" v)].
|
|
|
|
@interaction[#:eval the-eval
|
|
(catw "north")
|
|
(catw 'south)
|
|
(catw #"east")
|
|
(catw #\w)
|
|
(catw (list "red" 'green #"blue"))
|
|
]
|
|
}
|
|
|
|
@;{----------------------------------------}
|
|
|
|
@defproc[(catn [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]
|
|
[#:pos/exp-range
|
|
pos/exp-range
|
|
(list/c (or/c exact-integer? +inf.0)
|
|
(or/c exact-integer? -inf.0))
|
|
(list -inf.0 +inf.0)]
|
|
[#:exp-precision exp-precision
|
|
(or/c exact-nonnegative-integer?
|
|
(list/c '= exact-nonnegative-integer?))
|
|
5]
|
|
[#:exp-format-exponent exp-format-exponent
|
|
(or/c #f string? (-> exact-integer? string?))
|
|
#f]
|
|
[#:pad-digits-to pad-digits-to exact-positive-integer? 1]
|
|
[#:digits-padding digits-padding non-empty-string? " "])
|
|
string?]{
|
|
|
|
Converts the rational number @racket[x] to a string in either
|
|
positional or exponential notation. The exactness or inexactness of
|
|
@racket[x] does not affect its formatting.
|
|
|
|
Numbers whose order of magnitude (with respect to @racket[base]) fall
|
|
strictly within @racket[pos/exp-range] are formatted using positional
|
|
notation. More precisely, if the following condition holds:
|
|
|
|
@racketblock[(or (zero? x)
|
|
(< (expt base (car pos/exp-range))
|
|
(abs x)
|
|
(expt base (cadr pos/exp-range))))]
|
|
|
|
then the result is equivalent to
|
|
|
|
@racketblock[(catnp x
|
|
#:sign sign-mode
|
|
#:base base
|
|
#:precision precision
|
|
#:pad-digits-to pad-digits-to
|
|
#:digits-padding digits-padding)]
|
|
|
|
Otherwise, the number is formatted in exponential notation, and the
|
|
result is equivalent to
|
|
|
|
@racketblock[(catne x
|
|
#:sign sign-mode
|
|
#:base base
|
|
#:precision exp-precision
|
|
#:format-exponent exp-format-exponent
|
|
#:pad-digits-to pad-digits-to
|
|
#:digits-padding digits-padding)]
|
|
|
|
@examples[#:eval the-eval
|
|
(catn 999 #:pos/exp-range '(0 3))
|
|
(catn 1000 #:pos/exp-range '(0 3))
|
|
(catn 0.9876 #:pos/exp-range '(0 3))
|
|
]
|
|
|
|
Note that the default value of @racket[pos/exp-range] ensures that
|
|
positional notation will be used for any rational @racket[x].
|
|
}
|
|
|
|
|
|
@defproc[(catnp [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]
|
|
[#:pad-digits-to pad-digits-to exact-positive-integer? 1]
|
|
[#:digits-padding digits-padding non-empty-string? " "])
|
|
string?]{
|
|
|
|
Formats the rational number @racket[x] using positional notation according to
|
|
the following arguments:
|
|
|
|
@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}). 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
|
|
(catnp pi)
|
|
(catnp pi #:precision 4)
|
|
(catnp pi #:precision 0)
|
|
(catnp 1.5 #:precision 4)
|
|
(catnp 1.5 #:precision '(= 4))
|
|
(catnp 50 #:precision 2)
|
|
(catnp 50 #:precision '(= 2))
|
|
(catnp 50 #:precision '(= 0))
|
|
]}
|
|
|
|
@item{@racket[pad-digits-to]: if @racket[x] would normally be printed
|
|
with fewer than @racket[pad-digits-to] digits (including the decimal
|
|
point but not including the sign indicator), the output is left-padded
|
|
using @racket[digits-padding].
|
|
|
|
@interaction[#:eval the-eval
|
|
(catnp 17)
|
|
(catnp 17 #:pad-digits-to 4)
|
|
(catnp -42 #:pad-digits-to 4)
|
|
(catnp 1.5 #:pad-digits-to 4)
|
|
(catnp 1.5 #:precision 4 #:pad-digits-to 10)
|
|
(catnp 1.5 #:precision '(= 4) #:pad-digits-to 10)
|
|
]}
|
|
|
|
@item{@racket[digits-padding] specifies the string used to pad the
|
|
number to at least @racket[pad-digits-to] characters (not including the
|
|
sign indicator). The padding is placed between the sign and the normal
|
|
digits of @racket[x].
|
|
|
|
@interaction[#:eval the-eval
|
|
(catnp 17 #:pad-digits-to 4 #:digits-padding "0")
|
|
(catnp -42 #:pad-digits-to 4 #:digits-padding "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)]) (catnp 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)]) (catnp 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)]) (catnp 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)]) (catnp 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)]) (catnp 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
|
|
(catnp 100 #:base 7)
|
|
(catnp 4.5 #:base 2)
|
|
(catnp 3735928559 #:base 16)
|
|
(catnp 3735928559 #:base '(up 16))
|
|
]}
|
|
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(catne [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?))
|
|
5]
|
|
[#:format-exponent
|
|
format-exponent
|
|
(or/c #f string? (-> exact-integer? string?))
|
|
#f]
|
|
[#:pad-digits-to pad-digits-to exact-positive-integer? 1]
|
|
[#:digits-padding digits-padding non-empty-string? " "])
|
|
string?]{
|
|
|
|
Formats the rational number @racket[x] in exponential notation according to the
|
|
following arguments:
|
|
|
|
@itemlist[
|
|
|
|
@item{@racket[base], @racket[sign] are interpreted as in positional notation,
|
|
described above, except that they apply only to the significand, not the
|
|
exponent.
|
|
|
|
@interaction[#:eval the-eval
|
|
(catne -100 #:base 2)
|
|
]}
|
|
|
|
@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
|
|
(catne 1234 #: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
|
|
(catne 1234)
|
|
(catne 1234 #: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
|
|
(catne 1234 #:format-exponent (lambda (e) (format "E~a" e)))
|
|
]}
|
|
|
|
@item{@racket[precision] determines how many digits after the radix point the
|
|
significand contains. Like the @racket[precision] argument of @racket[catnp],
|
|
the form @racket[(list '= _digits)] causes trailing zeroes to be retained.
|
|
|
|
@interaction[#:eval the-eval
|
|
(catne 12345 #:precision 3)
|
|
(catne 12345 #:precision 2)
|
|
(catne 10000 #:precision 2)
|
|
(catne 10000 #:precision '(= 2))
|
|
]}
|
|
|
|
@item{@racket[pad-digits-to] and @racket[digits-padding] are interpreted as in
|
|
positional notation.
|
|
|
|
@interaction[#:eval the-eval
|
|
(catne 12345 #:pad-digits-to 12 #:digits-padding " ")
|
|
]}
|
|
|
|
]
|
|
|
|
@;{
|
|
Note that unlike @racket[string->number] (and thus @racket[cat]), @racket[catn]
|
|
does not use exponential notation for large (or small) inexact numbers. Large
|
|
numbers will be displayed with a large number of digits, many of which are not
|
|
significant.
|
|
|
|
@interaction[#:eval the-eval
|
|
(cat (expt 6.1 87))
|
|
(catn (expt 6.1 87))
|
|
(code:line (catn (+ (expt 6.1 87) 1000)) (code:comment "how many of these digits are significant?"))
|
|
]}
|
|
}
|
|
|
|
@(close-eval the-eval)
|