2021-05-20 10:53:11 +02:00
|
|
|
|
#lang typed/racket
|
2020-02-16 21:39:42 +01:00
|
|
|
|
|
2020-12-04 03:16:38 +01:00
|
|
|
|
(require (for-syntax syntax/parse racket/list))
|
2020-02-16 21:39:42 +01:00
|
|
|
|
|
2020-11-30 23:54:45 +01:00
|
|
|
|
(provide Symbol VariableMapping
|
2020-12-04 03:16:38 +01:00
|
|
|
|
eval-with eval1-with
|
2020-12-04 04:03:22 +01:00
|
|
|
|
extract-symbols
|
2020-12-05 00:33:14 +01:00
|
|
|
|
any->string stringify-variable-mapping string->any
|
2020-12-04 03:16:38 +01:00
|
|
|
|
;; Syntax
|
|
|
|
|
auto-hash-ref/explicit auto-hash-ref/:)
|
2020-02-16 21:39:42 +01:00
|
|
|
|
|
2020-05-16 23:09:00 +02:00
|
|
|
|
(module+ test
|
2020-11-30 23:54:45 +01:00
|
|
|
|
(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)))
|
2020-12-04 03:16:38 +01:00
|
|
|
|
|
|
|
|
|
(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)))
|
2020-12-04 04:03:22 +01:00
|
|
|
|
|
|
|
|
|
;;; ==============================
|
|
|
|
|
;;; Analysis of quoted expressions
|
|
|
|
|
;;; ==============================
|
|
|
|
|
|
|
|
|
|
;;; Produces a list of symbols appearing in the quoted expression
|
|
|
|
|
;;; passed in the first argument.
|
|
|
|
|
(: extract-symbols (-> Any (Listof Symbol)))
|
|
|
|
|
(define (extract-symbols form)
|
|
|
|
|
(: extract-rec (-> Any (Listof Any)))
|
|
|
|
|
(define (extract-rec form)
|
|
|
|
|
(match form
|
|
|
|
|
[(? symbol?) (list form)]
|
|
|
|
|
[(? list?)
|
|
|
|
|
(flatten (for/list : (Listof Any)
|
|
|
|
|
([x form])
|
|
|
|
|
(extract-symbols x)))]
|
|
|
|
|
[else '()]))
|
|
|
|
|
(cast (extract-rec form) (Listof Symbol)))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(test-case "extract-symbols"
|
|
|
|
|
(check-equal? (extract-symbols '(1 (2 3) x (y z 3)))
|
|
|
|
|
'(x y z))))
|
2020-12-04 23:43:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; =========================
|
|
|
|
|
;;; Org-mode interoperability
|
|
|
|
|
;;; =========================
|
|
|
|
|
|
|
|
|
|
(: any->string (-> Any String))
|
|
|
|
|
(define (any->string x)
|
|
|
|
|
(with-output-to-string (λ () (display x))))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(test-case "any->string"
|
|
|
|
|
(check-equal? (any->string 'a) "a")
|
|
|
|
|
(check-equal? (any->string '(a 1 (x y))) "(a 1 (x y))")
|
|
|
|
|
(check-equal? (any->string "hello") "hello")))
|
2020-12-05 00:22:49 +01:00
|
|
|
|
|
|
|
|
|
(: stringify-variable-mapping (-> (VariableMapping Any) (VariableMapping String)))
|
|
|
|
|
(define (stringify-variable-mapping ht)
|
|
|
|
|
(for/hash : (VariableMapping String)
|
|
|
|
|
([(key val) (in-hash ht)]) (values key (any->string val))))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(test-case "stringify-variable-mapping"
|
|
|
|
|
(define mp (stringify-variable-mapping #hash((a . (and a b)) (b . (not b)))))
|
|
|
|
|
(check-equal? (hash-ref mp 'a) "(and a b)")
|
|
|
|
|
(check-equal? (hash-ref mp 'b) "(not b)")))
|
2020-12-05 00:33:14 +01:00
|
|
|
|
|
|
|
|
|
(: string->any (-> String Any))
|
|
|
|
|
(define (string->any str)
|
|
|
|
|
(with-input-from-string str (λ () (read))))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(test-case "string->any"
|
|
|
|
|
(check-equal? (string->any "(or b (not a))") '(or b (not a)))
|
|
|
|
|
(check-equal? (string->any "14") 14)))
|
2020-12-06 21:58:49 +01:00
|
|
|
|
|
|
|
|
|
;;; Given a sexp, converts all "#f" to #f and "#t" to #t.
|
|
|
|
|
;;;
|
|
|
|
|
;;; When I read Org-mode tables, I pump them through a call to the
|
|
|
|
|
;;; prin1 because the elisp sexp seems incompatible with Racket. On
|
|
|
|
|
;;; the other hand, Racket Booleans seem to upset elisp a little, so
|
|
|
|
|
;;; prin1 wraps them in additional double quotes. This function
|
|
|
|
|
;;; removes those quotes.
|
|
|
|
|
(: handle-org-booleans (-> Any Any))
|
|
|
|
|
(define/match (handle-org-booleans datum)
|
|
|
|
|
[("#t") #t]
|
|
|
|
|
[("#f") #f]
|
|
|
|
|
[((? list?)) (map handle-org-booleans datum)]
|
|
|
|
|
[ (_) datum])
|