#lang racket
(require scheme/mpair)
(define (variant x)
(string->symbol
(regexp-replace #rx"^struct:"
(symbol->string (vector-ref (struct->vector x) 0))
"")))
(define (imaginary? z)
(and (complex? z)
(let ((zr (real-part z)))
(and (exact? zr) (zero? zr)))
))
(define (boolean-description bool)
(format "~s is a Boolean ~a"
bool (if bool "true" "false")))
(define small-integer-names
#("zero" "one"
"two"
"three"
"four"
"five"
"six"
"seven"
"eight"
"nine"
"ten"
"eleven"
"twelve"
"thirteen"
"fourteen"
"fifteen"
"sixteen"
"seventeen"
"eighteen"
"nineteen"))
(define (integer-0-19->string n)
(vector-ref small-integer-names n))
(define decade-names
#("zero" "ten"
"twenty"
"thirty"
"forty"
"fifty"
"sixty"
"seventy"
"eighty"
"ninety"))
(define (integer-0-99->string n)
(if (< n 20)
(integer-0-19->string n)
(let-values (((q10 r10) (quotient/remainder n 10)))
(if (= r10 0)
(vector-ref decade-names q10)
(string-append (vector-ref decade-names q10)
"-"
(vector-ref small-integer-names r10))))))
(define (integer-0-999->string n (include-and? #f))
(if (< n 100)
(integer-0-99->string n)
(let-values (((q100 r100) (quotient/remainder n 100)))
(string-append (vector-ref small-integer-names q100)
" hundred"
(if (= r100 0)
""
(string-append (if include-and? " and " " ")
(integer-0-99->string r100)))))))
(define thousands-names
#("zero" "thousand"
"million"
"billion"
"trillion"
"quadrillion"
"quintillion"
"sextillion"
"septillion"
"octillion"
"nonillion"
"decillion"
"undecillion"
"duodecillion"
"tredecillion"
"quattuordecillion"
"quindecillion"
"sexdecillion"
"septemdecillion"
"octdecillion"
"novemdecillion"
"vigintillion"
"unvigintillion"
"duovigintillion"
"tresvigintillion"
"quattuorvigintillion"
"quinquavigintillion"
"sesvigintillion"
"septenviginitillion"
"octovigintillion"
"novemvigintillion"
"trigintillion"
"untrigillion"
"duotrigillion"))
(define max-integer->string (expt 10 102))
(define (integer->string n)
(cond ((zero? n)
"zero")
((negative? n)
(string-append "minus " (integer->string (abs n))))
((< n 1000)
(integer-0-999->string n #t))
((< n max-integer->string)
(let/ec exit
(let loop ((str "")
(thousand-power 0)
(n n))
(if (= n 0)
(exit str)
(let-values (((q1000 r1000) (quotient/remainder n 1000)))
(loop (if (= thousand-power 0)
(if (= r1000 0)
""
(if (< r1000 20)
(string-append "and "
(integer-0-19->string r1000))
(integer-0-999->string r1000 #t)))
(if (= r1000 0)
str
(string-append (integer-0-999->string r1000)
" "
(vector-ref thousands-names thousand-power)
(if (> (string-length str) 0) " " "")
str)))
(+ thousand-power 1)
q1000))))))
(else
"at least 10^102")))
(define (exact-number-description z)
(cond ((fixnum? z)
(if (zero? z)
(format "~a is a byte (i.e., an exact positive integer fixnum between 0 and 255 inclusive) zero" z)
(if (byte? z)
(format "~s is a byte (i.e., an exact positive integer fixnum between 0 and 255 inclusive) ~a"
z (integer->string z))
(format "~s is an exact ~a integer fixnum ~a"
z (if (negative? z) "negative" "positive")
(integer->string z)))))
((and (integer? z) (< z max-integer->string))
(format "~s is an exact ~a integer ~a"
z (if (negative? z) "negative" "positive")
(integer->string z)))
((integer? z)
(format "~s is an exact ~a integer value whose absolute value is >= 10^102"
z (if (negative? z) "negative" "positive")))
((rational? z)
(format "~s is an exact ~a rational number with a numerator of ~a and a denominator of ~a"
z (if (negative? z) "negative" "positive")
(numerator z) (denominator z)))
((imaginary? z)
(format "~s is an exact ~a imaginary number"
z (if (negative? (imag-part z)) "negative" "positive")))
((complex? z)
(format "~s is an exact complex number whose magnitude is ~a"
z (magnitude z)))
(else
(format "~s is an exact number" z))))
(define (inexact-number-description z)
(cond ((integer? z)
(if (zero? z)
(format "~a is an inexact integer zero" z)
(format "~s is an inexact ~a integer"
z (if (negative? z) "negative" "positive"))))
((real? z)
(format "~s is an inexact ~a real number"
z (if (negative? z) "negative" "positive")))
((imaginary? z)
(format "~s is an inexact ~a imaginary number"
z (if (negative? (imag-part z)) "negative" "positive")))
((complex? z)
(format "~s is an inexact complex number whose magnitude is ~a"
z (magnitude z)))
(else
(format "~s is an inexact number" z))))
(define (number-description z)
(cond ((= z +inf.0)
(format "~s is positive infinity" z))
((= z -inf.0)
(format "~s is negative infinity" z))
((= z +nan.0)
(format "~s is not-a-number" z))
((exact? z)
(exact-number-description z))
((inexact? z)
(inexact-number-description z))
(else
(format "~s is a number" z))))
(define (string-description str)
(let ((len (string-length str)))
(if (= len 0)
(format "~s is an empty string" str)
(format "~s is ~a string of length ~a"
str (if (immutable? str) "an immutable" "a mutable") len))))
(define (byte-string-description bstr)
(let ((len (bytes-length bstr)))
(if (= len 0)
(format "~s is an empty byte string" bstr)
(format "~s is ~a byte string of length ~a"
bstr (if (immutable? bstr) "an immutable" "a mutable") len))))
(define general-category-alist
'((lu . "letter, uppercase")
(ll . "letter, lowercase")
(lt . "letter, titlecase")
(lm . "letter, modifier")
(lo . "letter, other")
(mn . "mark, nonspacing")
(mc . "mark, space combining")
(me . "mark, enclosing")
(nd . "number, decimal digit")
(nl . "number, letter")
(no . "number, other")
(ps . "punctuation, open")
(pe . "punctuation, close")
(pi . "punctuation, initial quote")
(pf . "punctuation, final quote")
(pd . "punctuation, dash")
(pc . "punctuation, connector")
(po . "punctuation, other")
(sc . "symbol, currency")
(sm . "symbol, math")
(sk . "symbol, modifier")
(so . "symbol, other")
(zs . "separator, space")
(zp . "separator, paragraph")
(zl . "separator, line")
(cc . "other, control")
(cf . "other, format")
(cs . "other, surrogate")
(co . "other, private use")
(cn . "other, not assigned")))
(define (general-category->string category)
(let ((category-assoc (assq category general-category-alist)))
(if category-assoc
(cdr category-assoc)
"unknown")))
(define (character-description char)
(let ((code-point (char->integer char))
(general-category (char-general-category char)))
(format "~s is a character whose code-point number is ~a(#x~x) and general category is '~a (~a)"
char code-point code-point
general-category (general-category->string general-category))))
(define (symbol-description sym)
(format "~s is ~a symbol"
sym (if (symbol-interned? sym) "an interned" "an uninterned")))
(define (regexp-description regexp)
(format "~s is a regular expression in ~a format"
regexp (if (pregexp? regexp) "pregexp" "regexp")))
(define (byte-regexp-description byte-regexp)
(format "~s is a byte regular expression in ~a format"
byte-regexp (if (byte-pregexp? byte-regexp) "pregexp" "regexp")))
(define (keyword-description kw)
(format "~s is a keyword" kw))
(define (list-description lst)
(if (null? lst)
(format "~s is an empty list" lst)
(format "~s is a proper immutable list of length ~a"
lst (length lst))))
(define (pair-description pair)
(format "~a is an improper immutable list" pair))
(define (mlist-description mlst)
(format "~s is a proper mutable list of length ~a"
mlst (mlength mlst)))
(define (mpair-description mpair)
(format "~a is an improper mutable list" mpair))
(define (vector-description v)
(let ((len (vector-length v)))
(if (= len 0)
(format "~s is an empty vector" v)
(format "~s is ~a vector of length ~a"
v (if (immutable? v) "an immutable" "a mutable") len))))
(define (box-description box)
(format "~s is a box containing ~s, ~a"
box (unbox box) (description (unbox box))))
(define (weak-box-description weak-box)
(format "~s is a weak box containing ~s, ~a"
weak-box (weak-box-value weak-box) (description (weak-box-value weak-box))))
(define (ephemeron-description eph)
(format "~s is an ephemeron containing ~s, ~a"
eph (ephemeron-value eph) (description (ephemeron-value eph))))
(define (hash-description hash)
(if (= (hash-count hash) 0)
(let ((type (if (hash-weak? hash)
"an empty mutable hash table that holds its keys weakly"
(if (immutable? hash)
"an empty immutable hash table"
"a empty mutable hash table")))
(compare (if (hash-eq? hash)
"eq?"
(if (hash-eqv? hash)
"eqv?"
"equal?"))))
(format "~s is ~a and that uses ~a to compare keys"
hash type compare))
(let ((type (if (hash-weak? hash)
"a mutable hash table that holds its keys weakly"
(if (immutable? hash)
"an immutable hash table"
"a mutable hash table")))
(compare (if (hash-eq? hash)
"eq?"
(if (hash-eqv? hash)
"eqv?"
"equal?"))))
(format "~s is ~a and that uses ~a to compare keys~a"
hash type compare
(for/fold ((key-text ""))
(((key value) (in-hash hash)))
(string-append key-text
(format "~n ~s : ~s, ~a"
key value (description value))))))))
(define (arity->string arity)
(cond ((integer? arity)
(number->string arity))
((arity-at-least? arity)
(format "at least ~a" (arity-at-least-value arity)))
(else
(let loop ((str "")
(tail arity))
(let ((arity (car tail)))
(if (null? (cdr tail))
(string-append str " or " (arity->string arity))
(loop (string-append str
(if (> (string-length str) 0) ", " "")
(arity->string arity))
(cdr tail))))))))
(define (keyword-list->string kw-lst)
(cond ((= (length kw-lst) 0)
"")
((= (length kw-lst) 1)
(string-append "#:" (keyword->string (car kw-lst))))
(else
(let/ec exit
(let loop ((str "")
(tail kw-lst))
(if (null? (cdr tail))
(exit (string-append str
" and "
"#:" (keyword->string (car tail))))
(loop (string-append str
(if (> (string-length str) 0) ", " "")
"#:" (keyword->string (car tail)))
(cdr tail))))))))
(define (procedure-arguments->string proc)
(let ((arity (procedure-arity proc)))
(let-values (((required accepted) (procedure-keywords proc)))
(format "accepts ~a ~a~a~a"
(arity->string arity) (if (eqv? arity 1) "argument" "arguments")
(if (null? required)
""
(format " with keyword ~a ~a"
(if (= (length required) 1) "argument" "arguments")
(keyword-list->string required)))
(if (null? accepted)
""
(format " plus optional keyword ~a ~a"
(if (= (length accepted) 1) "argument" "arguments")
(keyword-list->string accepted)))))))
(define (primitive-results->string prim)
(let ((arity (primitive-result-arity prim)))
(format "returns ~a ~a"
(arity->string arity) (if (eqv? arity 1) "result" "results"))))
(define (procedure-description proc)
(cond ((primitive? proc)
(let ((result-arity (procedure-arity proc)))
(format "~s is a primitive procedure ~athat ~a and ~a"
proc
(let ((name (object-name proc)))
(if name
(string-append "named "
(symbol->string name)
" ")
""))
(procedure-arguments->string proc)
(primitive-results->string proc))))
((primitive-closure? proc)
(format "~s is a primitive closure ~athat ~a"
proc
(let ((name (object-name proc)))
(if name
(string-append "named "
(symbol->string name))
""))
(procedure-arguments->string proc)))
(else
(format "~s is a procedure ~athat ~a"
proc
(let ((name (object-name proc)))
(if name
(string-append "named "
(symbol->string name)
" ")
""))
(procedure-arguments->string proc)))))
(define (port-description port)
(let ((direction (if (input-port? port)
(if (output-port? port)
"input-output"
"input")
(if (output-port? port)
"output"
"unknown"))))
(format "~s is ~a ~a port"
port (if (port-closed? port) "a closed" "an open")
direction)))
(define (path-description path)
(let ((convention (path-convention-type path)))
(format "~s is ~a ~a ~a path"
path
(if (complete-path? path) "a complete," "an incomplete,")
(if (absolute-path? path)
"absolute"
(if (relative-path? path)
"relative"
"unknown"))
convention)))
(define (structure-description struct)
(let ((name (object-name struct)))
(format "~s is a structure~a~a"
struct
(if name (format " of type ~a" name) "")
(for/fold ((str ""))
((field (in-vector (struct->vector struct)))
(i (in-naturals)))
(cond ((= i 0)
"")
((eq? field '...)
(string-append str (format "~n ...")))
(else
(string-append str (format "~n ~a : ~a, ~a"
i field (description field)))))))))
(define (description x)
(cond ((boolean? x)
(boolean-description x))
((number? x)
(number-description x))
((string? x)
(string-description x))
((bytes? x)
(byte-string-description x))
((char? x)
(character-description x))
((symbol? x)
(symbol-description x))
((regexp? x)
(regexp-description x))
((byte-regexp? x)
(byte-regexp-description x))
((keyword? x)
(keyword-description x))
((list? x)
(list-description x))
((pair? x)
(pair-description x))
((mlist? x)
(mlist-description x))
((mpair? x)
(mpair-description x))
((vector? x)
(vector-description x))
((box? x)
(box-description x))
((weak-box? x)
(weak-box-description x))
((hash? x)
(hash-description x))
((procedure? x)
(procedure-description x))
((port? x)
(port-description x))
((void? x)
(format "~s is void" x))
((eof-object? x)
(format "~s is an eof object" x))
((path? x)
(path-description x))
((struct? x)
(structure-description x))
(else
(let ((type (variant x))
(name (object-name x)))
(if (and object-name
(not (eq? type name)))
(format "~s is an object of type ~a named ~a"
x type name)
(format "~s is an object of type ~a"
x (variant x)))))))
(define (describe x)
(printf "~a~n" (description x)))
(provide/contract
(variant
(-> any/c symbol?))
(integer->string
(-> exact-integer? string?))
(description
(-> any/c string?))
(describe
(-> any/c void?)))