#lang racket/base
(define %numformat:host-negative-sign #\-)
(define %numformat:host-decimal-point #\.)
(define (%numformat:make-do-fractional-part decimal-point
max-fractional-length
pad-fractional-char
pad-fractional-length)
(lambda (chars out)
(if (and (or (not pad-fractional-length)
(zero? pad-fractional-length))
(or (null? chars) (equal? chars '(#\0))))
(void)
(begin
(display decimal-point out)
(if max-fractional-length
(begin (let loop ((remaining max-fractional-length)
(chars chars))
(cond ((null? chars))
((> remaining 0)
(write-char (car chars) out)
(loop (- remaining 1) (cdr chars)))))
(and pad-fractional-length
(let loop-pad ((remaining (- pad-fractional-length
(length chars))))
(and (> remaining 0)
(begin (write-char pad-fractional-char out)
(loop-pad (- remaining 1))))))
)
(begin
(for-each (lambda (c) (write-char c out)) chars)
))))))
(define (make-do-whole-part/spacers whole-spacers-char
whole-spacers-interval
do-fractional-part)
(lambda (chars out)
(let ((chars-after-dot #f))
(let ((len (let scan-whole ((chars chars)
(len 0))
(if (null? chars)
len
(let ((c (car chars)))
(cond ((char-numeric? c)
(scan-whole (cdr chars) (+ 1 len)))
((eqv? c %numformat:host-decimal-point)
(set! chars-after-dot (cdr chars))
len)
(else (error '<make-do-whole-part/spacers>
"invalid character ~S"
c))))))))
(let loop ((pos len)
(chars chars)
(group-pos #f))
(and (not (zero? pos))
(loop (- pos 1)
(cdr chars)
(cond ((not group-pos)
(display (car chars) out)
(and whole-spacers-interval
(modulo (- len 1) whole-spacers-interval)))
((zero? group-pos)
(display whole-spacers-char out)
(display (car chars) out)
(- whole-spacers-interval 1))
(else
(display (car chars) out)
(- group-pos 1)))))))
(and chars-after-dot
(do-fractional-part chars-after-dot out)
))))
(define-syntax %numformat:make-number-displayer/macro
(syntax-rules ()
((_ sign
prefix
pad-whole-char
pad-whole-length
whole-spacers-char
whole-spacers-interval
decimal-point
max-fractional-length
pad-fractional-char
pad-fractional-length
suffix)
(let* ((fractional-proc (%numformat:make-do-fractional-part
decimal-point
max-fractional-length
pad-fractional-char
pad-fractional-length))
(whole-proc (make-do-whole-part/spacers
whole-spacers-char
whole-spacers-interval
fractional-proc)))
(lambda (num (out (current-output-port)))
(let ((chars (string->list (number->string (exact->inexact num))))
(neg? #f))
(if (eqv? (car chars) %numformat:host-negative-sign)
(begin (set! neg? #t)
(set! chars (cdr chars))
(and (memq sign '(parens parens-spaces))
(write-char #\( out))
(and prefix (display prefix out))
(and (memq sign '(minus plus-minus))
(write-char #\- out)))
(begin (and (eq? sign 'parens-spaces)
(write-char #\space out))
(and prefix
(display prefix out))
(and (eq? sign 'plus-minus)
(not (zero? num))
(write-char #\+ out))))
(whole-proc chars out)
(case sign
((parens) (and neg? (write-char #\) out)))
((parens-spaces) (write-char (if neg? #\) #\space) out)))))))))
(define %numformat:make-number-displayer/positional
(lambda (sign
prefix
pad-whole-char
pad-whole-length
whole-spacers-char
whole-spacers-interval
decimal-point
max-fractional-length
pad-fractional-char
pad-fractional-length
suffix)
(%numformat:make-number-displayer/macro
sign
prefix
pad-whole-char
pad-whole-length
whole-spacers-char
whole-spacers-interval
decimal-point
max-fractional-length
pad-fractional-char
pad-fractional-length
suffix)))
(define make-number-displayer
(letrec ((pc (lambda (x)
(cond ((not x) x)
((char? x) x)
((string? x) (case (string-length x)
((0) #f)
((1) (string-ref x 0))
(else (error 'make-number-displayer
"pc: invalid string x=~S"
x))))
(else (error 'make-number-displayer
"pc: invalid x=~S type"
x)))))
(p0 (lambda (x)
(if (or (not x) (and (integer? x) (>= x 0)))
x
(error 'make-number-displayer
"p0: expected x=~S to be #f or integer >= 0"
x))))
(p1 (lambda (x)
(if (or (not x) (and (integer? x) (>= x 1)))
x
(error 'make-number-displayer
"p1: expected x=~S to be #f or integer >= 1" x))))
(ps (lambda (x)
(cond ((not x) x)
((char? x) x)
((string? x) (case (string-length x)
((0) #f)
((1) (string-ref x 0))
(else (string-copy x))))
(else (error 'make-number-displayer
"ps: invalid x=~S type"
x)))))
(pe (lambda (x acceptable)
(if (memv x acceptable)
x
(error 'make-number-displayer
"pe: x=~S is not acceptable"
x)))))
(lambda (specs)
(let ((sign 'minus)
(prefix #f)
(pad-whole-char #f)
(pad-whole-length #f)
(whole-spacers-char #f)
(whole-spacers-interval #f)
(decimal-point #\.)
(max-fractional-length #f)
(pad-fractional-char 0)
(pad-fractional-length #f)
(suffix #f))
(for-each
(lambda (item)
(or (and (list? item) (= (length item) 2))
(error 'make-number-displayer
"spec attributes must be list of 2 elements: ~S"
item))
(let ((attr (car item))
(v (cadr item)))
(case attr
((sign)
(set! sign (pe v '(minus plus-minus parens parens-spaces))))
((prefix) (set! prefix (ps v)))
((pad-whole-char) (set! pad-whole-char (pc v)))
((pad-whole-length) (set! pad-whole-length (p1 v)))
((whole-spacers-char) (set! whole-spacers-char (pc v)))
((whole-spacers-interval) (set! whole-spacers-interval (p1 v)))
((decimal-point) (set! decimal-point (ps v)))
((max-fractional-length) (set! max-fractional-length (p0 v)))
((pad-fractional-char) (set! pad-fractional-char (pc v)))
((pad-fractional-length) (set! pad-fractional-length (p1 v)))
((suffix) (set! suffix (ps v)))
(else (error 'make-number-displayer
"invalid spec attribute name: ~S"
attr)))))
specs)
(%numformat:make-number-displayer/positional
sign
prefix
pad-whole-char
pad-whole-length
whole-spacers-char
whole-spacers-interval
decimal-point
max-fractional-length
pad-fractional-char
pad-fractional-length
suffix)))))
(define display-number/us-style
(make-number-displayer
'((whole-spacers-char #\,)
(whole-spacers-interval 3)
(decimal-point #\.))))
(define display-number/european-style
(make-number-displayer
'((whole-spacers-char #\')
(whole-spacers-interval 3)
(decimal-point #\,))))
(provide make-number-displayer
display-number/us-style
display-number/european-style)