Skip to content

Instantly share code, notes, and snippets.

@allison-casey
Created November 24, 2020 01:48
Show Gist options
  • Save allison-casey/754abbafc0b9e8089abe5f6763349f1a to your computer and use it in GitHub Desktop.
Save allison-casey/754abbafc0b9e8089abe5f6763349f1a to your computer and use it in GitHub Desktop.
iterable unpacking let macro
(defmacro let [bindings &rest body]
"
sets up lexical bindings in its body
Bindings are processed sequentially,
so you can use the result of an earlier binding in a later one.
Basic assignments (e.g. setv, +=) will update the let binding,
if they use the name of a let binding.
But assignments via `import` are always hoisted to normal Python scope, and
likewise, `defclass` will assign the class to the Python scope,
even if it shares the name of a let binding.
Use `import_module` and `type` (or whatever metaclass) instead,
if you must avoid this hoisting.
Function arguments can shadow let bindings in their body,
as can nested let forms.
"
(if (odd? (len bindings))
(macro-error bindings "let bindings must be paired"))
(setv g!let (gensym 'let)
replacements (OrderedDict)
unpacked-syms (OrderedDict)
keys []
values [])
(defn expander [symbol]
(.get replacements symbol symbol))
(defn destructuring-expander [symbol]
(cond
[(not (symbol? symbol)) (macro-error symbol "bind targets must be symbol or destructing assignment")]
[(in '. symbol) (macro-error symbol "binding target may not contain a dot")])
(setv replaced (gensym symbol))
(assoc unpacked-syms symbol replaced)
replaced)
(defn destructuring? [x]
(or (instance? HyList x)
(and (instance? HyExpression x)
(= (first x) ',))))
(for [[k v] (partition bindings)]
(cond
[(and (symbol? k) (in '. k))
(macro-error k "binding target may not contain a dot")]
[(not (or (symbol? k) (destructuring? k)))
(macro-error k "bind targets must be symbol or iterable unpacking assignment")])
(if (destructuring? k)
(do
;; append the setv unpacking form
(.append keys (symbolexpand (macroexpand-all k &name) destructuring-expander))
(.append values (symbolexpand (macroexpand-all v &name) expander))
;; add the keys we replaced in the unpacking form into the let
;; dict
(prewalk (fn [x]
(cond
[(and (symbol? x) (in '. x))
(macro-error k "bind target may not contain a dot")]
[(and (instance? HyExpression x) (-> x first (in #{', 'unpack-iterable}) not))
(macro-error k "cannot destructure non-iterable unpacking expression")]
[(and (symbol? x) (in x unpacked-syms))
(do (.append keys `(get ~g!let ~(name x)))
(.append values (.get unpacked-syms x x))
(assoc replacements x (last keys)))]
[True x]))
k))
(do (.append values (symbolexpand (macroexpand-all v &name) expander))
(.append keys `(get ~g!let ~(name k)))
(assoc replacements k (last keys)))))
`(do
(setv ~g!let {}
~@(interleave keys values))
~@(symbolexpand (macroexpand-all body &name)
expander)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment