#lang typed/racket (require (for-syntax syntax/parse racket/list)) (provide Symbol VariableMapping eval-with eval1-with ;; Syntax auto-hash-ref/explicit auto-hash-ref/:) (module+ test (require typed/rackunit)) ;;; =================== ;;; HashTable injection ;;; =================== (define-type Variable Symbol) (define-type (VariableMapping A) (Immutable-HashTable Variable A)) (: eval-with (-> (VariableMapping Any) Any AnyValues)) (define (eval-with ht expr) (parameterize ([current-namespace (make-base-namespace)]) (for ([(x val) (in-hash ht)]) (namespace-set-variable-value! x val)) (eval expr))) (: eval1-with (-> (VariableMapping Any) Any Any)) (define (eval1-with ht expr) (call-with-values (λ () (eval-with ht expr)) (λ args (car args)))) (module+ test (test-case "eval-with" (check-equal? (eval1-with (hash 'a 1 'b 2) '(+ a b 1)) 4) (define ht : (VariableMapping Integer) (hash 'a 1 'b 2)) (define expr : Any '(+ a b 1)) (check-equal? (eval1-with ht expr) 4))) (define-syntax (auto-hash-ref/explicit stx) (syntax-parse stx [(_ (ht:id xs:id ...) body:expr) #`(let #,(for/list ([x (syntax->list #'(xs ...))]) #`[#,x (hash-ref ht '#,x)]) body)])) (module+ test (test-case "auto-hash-ref/explicit" (define mytable #hash((a . 3) (b . 4))) (check-equal? (auto-hash-ref/explicit (mytable b a) (* a b)) 12) (define ht #hash((a . #t) (b . #f))) (check-equal? (auto-hash-ref/explicit (ht a b) (and (not a) b)) #f))) (define-syntax (auto-hash-ref/: stx) (syntax-parse stx [(_ ht:id body) (let* ([names/: (collect-colons (syntax->datum #'body))]) #`(let #,(for/list ([x names/:]) ;; put x in the same context as body #`[#,(datum->syntax #'body x) (hash-ref ht '#,(strip-colon x))]) body))])) ;;; The helper functions for auto-hash-ref/:. (begin-for-syntax ;; Collect all the symbols starting with a colon in datum. (define (collect-colons datum) (remove-duplicates (flatten (for/list ([token datum]) (cond [(symbol? token) (let ([name (symbol->string token)]) (if (eq? #\: (string-ref name 0)) token '()))] [(list? token) (collect-colons token)] [else '()]))))) ;; Strip the leading colon off x. (define (strip-colon x) (let ([x-str (symbol->string x)]) (if (eq? #\: (string-ref x-str 0)) (string->symbol (substring x-str 1)) x)))) (module+ test (test-case "auto-hash-ref/:" (define ht1 #hash((x . #t) (y . #t) (t . #f))) (define z #t) (check-equal? (auto-hash-ref/: ht1 (and :x (not :y) z (or (and :t) :x))) #f) (define ht2 #hash((a . 1) (b . 2))) (check-equal? (auto-hash-ref/: ht2 (+ :a (* 2 :b))) 5)))