#lang racket/base (provide format-datum format-datums datum?) (define (blank? str) (for/and ([c (in-string str)]) (char-blank? c))) ;; read "foo bar" the same way as "(foo bar)" ;; otherwise "bar" is dropped, which is too astonishing (define (string->datum str) (unless (blank? str) (let ([result (read (open-input-string (format "(~a)" str)))]) (if (= (length result) 1) (car result) result)))) (define (datum? x) (or (list? x) (symbol? x))) (define (format-datum datum-template . args) (unless (datum? datum-template) (raise-argument-error 'format-datums "datum?" datum-template)) (string->datum (apply format (format "~a" datum-template) (map (λ (arg) (if (syntax? arg) (syntax->datum arg) arg)) args)))) (define (format-datums datum-template . argss) (unless (datum? datum-template) (raise-argument-error 'format-datums "datum?" datum-template)) (apply map (λ args (apply format-datum datum-template args)) argss)) (module+ test (require rackunit syntax/datum) (check-equal? (string->datum "foo") 'foo) (check-equal? (string->datum "(foo bar)") '(foo bar)) (check-equal? (string->datum "foo bar") '(foo bar)) (check-equal? (string->datum "42") 42) (check-equal? (format-datum '(~a-bar-~a) "foo" "zam") '(foo-bar-zam)) (check-equal? (format-datum '(~a-bar-~a) #'foo #'zam) '(foo-bar-zam)) (check-equal? (format-datum (datum (~a-bar-~a)) "foo" "zam") '(foo-bar-zam)) (check-equal? (format-datum '~a "foo") 'foo) (check-equal? (format-datum '~a "foo") 'foo) (check-equal? (format-datum '~a "") (void)) (check-equal? (format-datum '~a " ") (void)) (check-equal? (format-datums '(put ~a) '("foo" "zam")) '((put foo) (put zam))))