2020-11-30 22:56:19 +01:00
|
|
|
|
#lang typed/racket
|
2020-02-16 21:39:42 +01:00
|
|
|
|
|
2020-12-16 23:21:33 +01:00
|
|
|
|
(require (for-syntax syntax/parse racket/list))
|
2020-02-16 21:39:42 +01:00
|
|
|
|
|
2020-12-06 23:42:59 +01:00
|
|
|
|
(provide Variable VariableMapping GeneralPair
|
2020-12-04 03:16:38 +01:00
|
|
|
|
eval-with eval1-with
|
2020-12-04 04:03:22 +01:00
|
|
|
|
extract-symbols
|
2020-12-06 21:59:28 +01:00
|
|
|
|
any->string stringify-variable-mapping string->any map-sexp
|
2020-12-12 23:24:46 +01:00
|
|
|
|
read-org-sexp unorg unstringify-pairs compose-n compose-3 compose-4
|
2020-12-16 23:09:35 +01:00
|
|
|
|
multi-compose read-org-variable-mapping unorgv
|
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
|
|
|
|
|
|
|
|
|
|
2020-12-12 23:35:40 +01:00
|
|
|
|
;;; =========================
|
|
|
|
|
;;; Composing typed functions
|
|
|
|
|
;;; =========================
|
|
|
|
|
|
|
|
|
|
(: compose-n (All (a) (-> (-> a a) * (-> a a))))
|
|
|
|
|
(define (compose-n . funcs)
|
|
|
|
|
(λ (x)
|
|
|
|
|
(for/foldr ([x x]) ([f funcs])
|
|
|
|
|
(f x))))
|
|
|
|
|
|
|
|
|
|
(: compose-3 (All (a b c d) (-> (-> c d) (-> b c) (-> a b) (-> a d))))
|
|
|
|
|
(define (compose-3 f1 f2 f3)
|
|
|
|
|
(λ (x) (f1 (f2 (f3 x)))))
|
|
|
|
|
(: compose-4 (All (a b c d e) (-> (-> d e) (-> c d) (-> b c) (-> a b) (-> a e))))
|
|
|
|
|
(define (compose-4 f1 f2 f3 f4)
|
|
|
|
|
(λ (x) (f1 (f2 (f3 (f4 x))))))
|
|
|
|
|
|
2020-12-16 23:09:35 +01:00
|
|
|
|
(define-syntax (multi-compose stx)
|
2020-12-16 23:21:33 +01:00
|
|
|
|
;; Implementation by Sorawee Porncharoenwase.
|
2020-12-16 23:09:35 +01:00
|
|
|
|
(syntax-parse stx
|
2020-12-16 23:21:33 +01:00
|
|
|
|
[(_ f:expr g:expr)
|
|
|
|
|
#'(compose f g)]
|
|
|
|
|
[(_ f:expr funcs:expr ...)
|
|
|
|
|
#'(compose f (multi-compose funcs ...))]))
|
2020-12-16 23:09:35 +01:00
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-equal? ((multi-compose add1
|
|
|
|
|
(λ ([x : Number]) (* x 3))
|
|
|
|
|
add1
|
|
|
|
|
(λ ([x : Number]) (+ x 2)))
|
|
|
|
|
3)
|
|
|
|
|
19))
|
|
|
|
|
|
2020-12-12 23:35:40 +01:00
|
|
|
|
|
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])
|
2020-12-06 21:59:28 +01:00
|
|
|
|
|
|
|
|
|
(: map-sexp (-> (-> Any Any) Any Any))
|
|
|
|
|
(define (map-sexp func sexp)
|
|
|
|
|
(match sexp
|
|
|
|
|
[(? list?) (map ((curry map-sexp) func) sexp)]
|
|
|
|
|
[datum (func datum)]))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(test-case "map-sexp"
|
|
|
|
|
(check-equal? (map-sexp (λ (x) (add1 (cast x Number))) '(1 2 (4 10) 3))
|
|
|
|
|
'(2 3 (5 11) 4))))
|
2020-12-06 22:28:45 +01:00
|
|
|
|
|
|
|
|
|
(: read-org-sexp (-> String Any))
|
|
|
|
|
(define read-org-sexp
|
|
|
|
|
(compose ((curry map-sexp) (match-lambda
|
|
|
|
|
[(and (? string?) str) (string->any str)]
|
|
|
|
|
[x x]))
|
|
|
|
|
string->any))
|
|
|
|
|
(define unorg read-org-sexp)
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(test-case "read-org-sexp"
|
|
|
|
|
(check-equal? (read-org-sexp "((\"a\" \"(and a b)\") (\"b\" \"(or b (not a))\"))")
|
|
|
|
|
'((a (and a b)) (b (or b (not a)))))
|
|
|
|
|
(check-equal? (unorg "(#t \"#t\" \"#t \" '(1 2 \"#f\"))")
|
|
|
|
|
'(#t #t #t '(1 2 #f)))))
|
2020-12-06 23:42:59 +01:00
|
|
|
|
|
|
|
|
|
(define-type (GeneralPair A B) (U (Pair A B) (List A B)))
|
|
|
|
|
|
|
|
|
|
(: unstringify-pairs (-> (Listof (GeneralPair String Any))
|
|
|
|
|
(Listof (GeneralPair Symbol Any))))
|
|
|
|
|
(define (unstringify-pairs pairs)
|
|
|
|
|
(for/list ([p pairs])
|
|
|
|
|
(match p
|
|
|
|
|
[(list key val)
|
|
|
|
|
(cons (string->symbol key) (if (string? val)
|
|
|
|
|
(string->any val)
|
|
|
|
|
val))]
|
|
|
|
|
[(cons key val)
|
|
|
|
|
(cons (string->symbol key) (if (string? val)
|
|
|
|
|
(string->any val)
|
|
|
|
|
val))])))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(test-case "unstringify-pairs"
|
|
|
|
|
(check-equal? (unstringify-pairs '(("a" . "1") ("b" . "(and a (not b))")))
|
|
|
|
|
'((a . 1) (b . (and a (not b)))))
|
|
|
|
|
(check-equal? (unstringify-pairs '(("a" . 1) ("b" . "(and a (not b))")))
|
|
|
|
|
'((a . 1) (b . (and a (not b)))))))
|
2020-12-12 23:25:11 +01:00
|
|
|
|
|
|
|
|
|
(: read-org-variable-mapping (-> String (VariableMapping Any)))
|
|
|
|
|
(define read-org-variable-mapping
|
|
|
|
|
(compose-3
|
|
|
|
|
(λ ([pairs : (Listof (Pair Symbol Any))])
|
|
|
|
|
(make-immutable-hash pairs))
|
|
|
|
|
(λ (sexp)
|
|
|
|
|
(unstringify-pairs (cast sexp (Listof (GeneralPair String Any)))))
|
|
|
|
|
string->any))
|
|
|
|
|
|
|
|
|
|
;;; A synonym for read-org-variable-mapping.
|
|
|
|
|
(define unorgv read-org-variable-mapping)
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(test-case "read-org-variable-mapping"
|
|
|
|
|
(define m1 (read-org-variable-mapping "((\"a\" \"(and a b)\") (\"b\" \"(or b (not a))\"))"))
|
|
|
|
|
(define m2 (read-org-variable-mapping "((\"a\" . \"(and a b)\") (\"b\" . \"(or b (not a))\"))"))
|
|
|
|
|
(define m3 (unorgv "((\"a\" . \"(and a b)\") (\"b\" . \"(or b (not a))\"))"))
|
|
|
|
|
(check-equal? (hash-ref m1 'a) '(and a b))
|
|
|
|
|
(check-equal? (hash-ref m2 'a) '(and a b))
|
|
|
|
|
(check-equal? (hash-ref m3 'a) '(and a b))
|
|
|
|
|
(check-equal? (hash-ref m1 'b) '(or b (not a)))
|
|
|
|
|
(check-equal? (hash-ref m2 'b) '(or b (not a)))
|
|
|
|
|
(check-equal? (hash-ref m3 'b) '(or b (not a)))))
|