[format] strongly-typed format + printf
This commit is contained in:
parent
7169d7a4a9
commit
a5cae0565e
91
format.rkt
Normal file
91
format.rkt
Normal file
|
@ -0,0 +1,91 @@
|
|||
#lang typed/racket/base
|
||||
|
||||
(provide
|
||||
format!
|
||||
;; (-> (x:String) Any *N Void)
|
||||
;; Takes 1 required string argument and N additional arguments,
|
||||
;; where N is the number of format sequences in the string.
|
||||
;;
|
||||
;; If the string is a literal, raises a compile-time arity error if
|
||||
;; the given number of arguments does not match the format string.
|
||||
;;
|
||||
;; If the string is not a literal, arity-checking happens at runtime.
|
||||
|
||||
printf!
|
||||
;; (-> (x:String) Any *N Void)
|
||||
;; Similar to `format`, but displays the formatted string to `current-output-port`.
|
||||
)
|
||||
|
||||
(require
|
||||
(for-syntax typed/racket/base syntax/parse racket/sequence))
|
||||
|
||||
;; =============================================================================
|
||||
|
||||
(define-syntax format!
|
||||
(syntax-parser
|
||||
[(f template:str arg* ...)
|
||||
(let ([num-expected (count-format-escapes (syntax-e #'template))]
|
||||
[num-given (for/sum ([a (in-syntax #'(arg* ...))]) 1)])
|
||||
(unless (= num-expected num-given)
|
||||
(raise-arity-error
|
||||
(syntax-e #'f)
|
||||
num-expected
|
||||
(for/list ([a (in-syntax #'(arg* ...))]) (syntax->datum a))))
|
||||
(syntax/loc #'f (format template arg* ...)))]
|
||||
[(f tmp arg* ...)
|
||||
(syntax/loc #'f (format tmp arg* ...))]))
|
||||
|
||||
(define-syntax-rule (printf! arg* ...)
|
||||
(display (format! arg* ...)))
|
||||
|
||||
;; Count the number of format escapes in a string.
|
||||
(define-for-syntax (count-format-escapes str)
|
||||
(define last-index (- (string-length str) 1))
|
||||
(let loop ([i 0] [acc 0])
|
||||
(cond
|
||||
[(>= i last-index)
|
||||
acc]
|
||||
[(eq? #\~ (string-ref str i))
|
||||
;; From fprintf docs
|
||||
(case (string-ref str (+ i 1))
|
||||
[(#\n #\% #\a #\A #\v #\V #\e #\E #\c #\C #\b #\B #\o #\O #\x #\X #\space #\tab #\newline)
|
||||
(loop (+ i 2) (+ 1 acc))]
|
||||
[(#\.)
|
||||
(if (and (< (+ 1 i) last-index)
|
||||
(memq (string-ref str (+ i 2)) '(#\a #\A #\s #\S #\v #\V)))
|
||||
(loop (+ i 3) (+ 1 acc))
|
||||
(loop (+ i 3) acc))]
|
||||
[else
|
||||
(loop (+ i 2) acc)])]
|
||||
[else
|
||||
(loop (+ i 1) acc)])))
|
||||
|
||||
;; =============================================================================
|
||||
|
||||
(module+ test (begin-for-syntax
|
||||
|
||||
(require
|
||||
rackunit)
|
||||
|
||||
;; --- count-format-escapes
|
||||
(define-syntax-rule (test-escapes [str n] ...)
|
||||
(begin
|
||||
(check-equal? (count-format-escapes str) n) ...))
|
||||
|
||||
(test-escapes
|
||||
["" 0]
|
||||
["hello" 0]
|
||||
["world" 0]
|
||||
["~~ nope" 0]
|
||||
["~" 0]
|
||||
["\\~~" 0]
|
||||
["~~~~" 0]
|
||||
["hey ~.x you" 0]
|
||||
;; --
|
||||
["hello, ~a" 1]
|
||||
["~a ~b ~c" 3]
|
||||
["~\n ~%" 2]
|
||||
["~a ~A ~v ~V ~e ~E ~c ~C ~b ~B ~o ~O ~x ~X" 14]
|
||||
["~ " 1])
|
||||
))
|
||||
|
Loading…
Reference in New Issue
Block a user