[format] change naming, fix broken format sequences, add typechecking
This commit is contained in:
parent
c5ea8c032c
commit
a26f7a79b4
87
format.rkt
87
format.rkt
|
@ -1,7 +1,7 @@
|
||||||
#lang typed/racket/base
|
#lang typed/racket/base
|
||||||
|
|
||||||
(provide
|
(provide
|
||||||
format!
|
format:
|
||||||
;; (-> (x:String) Any *N Void)
|
;; (-> (x:String) Any *N Void)
|
||||||
;; Takes 1 required string argument and N additional arguments,
|
;; Takes 1 required string argument and N additional arguments,
|
||||||
;; where N is the number of format sequences in the string.
|
;; where N is the number of format sequences in the string.
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
;;
|
;;
|
||||||
;; If the string is not a literal, arity-checking happens at runtime.
|
;; If the string is not a literal, arity-checking happens at runtime.
|
||||||
|
|
||||||
printf!
|
printf:
|
||||||
;; (-> (x:String) Any *N Void)
|
;; (-> (x:String) Any *N Void)
|
||||||
;; Similar to `format`, but displays the formatted string to `current-output-port`.
|
;; Similar to `format`, but displays the formatted string to `current-output-port`.
|
||||||
)
|
)
|
||||||
|
@ -21,71 +21,70 @@
|
||||||
|
|
||||||
;; =============================================================================
|
;; =============================================================================
|
||||||
|
|
||||||
(define-syntax format!
|
(define-syntax format:
|
||||||
(syntax-parser
|
(syntax-parser
|
||||||
[(f template:str arg* ...)
|
[(f template:str arg* ...)
|
||||||
(let ([num-expected (count-format-escapes (syntax-e #'template))]
|
;; 1. Parse expected types from the template
|
||||||
[num-given (for/sum ([a (in-syntax #'(arg* ...))]) 1)])
|
(let* ([type* (template->type* (syntax-e #'template) #:src #'f)]
|
||||||
|
[num-expected (length type*)]
|
||||||
|
[num-given (for/sum ([a (in-syntax #'(arg* ...))]) 1)])
|
||||||
(unless (= num-expected num-given)
|
(unless (= num-expected num-given)
|
||||||
(raise-arity-error
|
(raise-arity-error
|
||||||
(syntax-e #'f)
|
(syntax-e #'f)
|
||||||
num-expected
|
num-expected
|
||||||
(for/list ([a (in-syntax #'(arg* ...))]) (syntax->datum a))))
|
(for/list ([a (in-syntax #'(arg* ...))]) (syntax->datum a))))
|
||||||
(syntax/loc #'f (format template arg* ...)))]
|
;; 2. If any types left obligations, use `ann` to typecheck the args
|
||||||
|
(let ([arg+*
|
||||||
|
(for/list ([a (in-syntax #'(arg* ...))]
|
||||||
|
[t (in-list type*)])
|
||||||
|
(if t (quasisyntax/loc #'f (ann #,a #,t)) a))])
|
||||||
|
(quasisyntax/loc #'f
|
||||||
|
(format template #,@arg+*))))]
|
||||||
[(f tmp arg* ...)
|
[(f tmp arg* ...)
|
||||||
(syntax/loc #'f (format tmp arg* ...))]))
|
(syntax/loc #'f (format tmp arg* ...))]))
|
||||||
|
|
||||||
(define-syntax-rule (printf! arg* ...)
|
(define-syntax-rule (printf: arg* ...)
|
||||||
(display (format! arg* ...)))
|
(display (format: arg* ...)))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------------
|
||||||
|
|
||||||
;; Count the number of format escapes in a string.
|
;; Count the number of format escapes in a string.
|
||||||
(define-for-syntax (count-format-escapes str)
|
;; Returns a list of optional types (to be spliced into the source code)
|
||||||
|
;; Example: If result is '(#f Integer), then
|
||||||
|
;; - Expect 2 arguments to format string
|
||||||
|
;; - First argument has no constraints, second must be an Integer
|
||||||
|
;; (: count-format-escapes (->* [String] [#:src (U #f Syntax)] (Listof (U #f Syntax))))
|
||||||
|
(define-for-syntax (template->type* str #:src [stx #f])
|
||||||
(define last-index (- (string-length str) 1))
|
(define last-index (- (string-length str) 1))
|
||||||
(let loop ([i 0] [acc 0])
|
(let loop ([i 0] [acc '()])
|
||||||
(cond
|
(cond
|
||||||
[(>= i last-index)
|
[(>= i last-index)
|
||||||
acc]
|
(reverse acc)]
|
||||||
[(eq? #\~ (string-ref str i))
|
[(eq? #\~ (string-ref str i))
|
||||||
;; From fprintf docs
|
;; From fprintf docs
|
||||||
(case (string-ref str (+ i 1))
|
(case (string-ref str (+ i 1))
|
||||||
[(#\n #\% #\a #\A #\v #\V #\e #\E #\c #\C #\b #\B #\o #\O #\x #\X #\space #\tab #\newline)
|
[(#\% #\n #\~ #\space #\tab #\newline)
|
||||||
(loop (+ i 2) (+ 1 acc))]
|
;; Need 0 arguments
|
||||||
|
(loop (+ i 2) acc)]
|
||||||
|
[(#\a #\A #\s #\S #\v #\V #\e #\E)
|
||||||
|
;; Need 1 argument, can be anything
|
||||||
|
(loop (+ i 2) (cons #f acc))]
|
||||||
[(#\.)
|
[(#\.)
|
||||||
|
;; Need at most 1, can be anything
|
||||||
(if (and (< (+ 1 i) last-index)
|
(if (and (< (+ 1 i) last-index)
|
||||||
(memq (string-ref str (+ i 2)) '(#\a #\A #\s #\S #\v #\V)))
|
(memq (string-ref str (+ i 2)) '(#\a #\A #\s #\S #\v #\V)))
|
||||||
(loop (+ i 3) (+ 1 acc))
|
(loop (+ i 3) (cons #f acc))
|
||||||
(loop (+ i 3) acc))]
|
(loop (+ i 3) acc))]
|
||||||
|
[(#\c #\C)
|
||||||
|
;; Need 1 `char?`
|
||||||
|
(loop (+ i 2) (cons (syntax/loc stx Char) acc))]
|
||||||
|
[(#\b #\B #\o #\O #\x #\X)
|
||||||
|
;; Need 1 `exact?`
|
||||||
|
(loop (+ i 2) (cons (syntax/loc stx Exact-Number) acc))]
|
||||||
[else
|
[else
|
||||||
(loop (+ i 2) acc)])]
|
;; Invalid format sequence
|
||||||
|
(raise-user-error "format: unrecognized pattern string '~~~c'"
|
||||||
|
(string-ref str (+ i 1)))])]
|
||||||
[else
|
[else
|
||||||
(loop (+ i 1) acc)])))
|
(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