Make assoc a macro instead of a special form

The new macro evaluates its lvalue only once.
This commit is contained in:
Kodi Arfer 2017-08-11 10:40:46 -07:00
parent 062e24d71f
commit 2bbf886ceb
5 changed files with 35 additions and 27 deletions

1
NEWS
View File

@ -32,6 +32,7 @@ Changes from 0.13.0
* try form now possible in defmacro/deftag * try form now possible in defmacro/deftag
* Fixed a crash when `with` suppresses an exception. `with` now returns * Fixed a crash when `with` suppresses an exception. `with` now returns
`None` in this case. `None` in this case.
* `assoc` now evaluates its arguments only once each
[ Misc. Improvements ] [ Misc. Improvements ]
* `read`, `read_str`, and `eval` are exposed and documented as top-level * `read`, `read_str`, and `eval` are exposed and documented as top-level

View File

@ -1406,30 +1406,6 @@ class HyASTCompiler(object):
step=step.expr), step=step.expr),
ctx=ast.Load()) ctx=ast.Load())
@builds("assoc")
@checkargs(min=3, even=False)
def compile_assoc_expression(self, expr):
expr.pop(0) # assoc
# (assoc foo bar baz) => foo[bar] = baz
target = self.compile(expr.pop(0))
ret = target
i = iter(expr)
for (key, val) in ((self.compile(x), self.compile(y))
for (x, y) in zip(i, i)):
ret += key + val + ast.Assign(
lineno=expr.start_line,
col_offset=expr.start_column,
targets=[
ast.Subscript(
lineno=expr.start_line,
col_offset=expr.start_column,
value=target.force_expr,
slice=ast.Index(value=key.force_expr),
ctx=ast.Store())],
value=val.force_expr)
return ret
@builds("with_decorator") @builds("with_decorator")
@checkargs(min=1) @checkargs(min=1)
def compile_decorate_expression(self, expr): def compile_decorate_expression(self, expr):

View File

@ -328,9 +328,9 @@
(do (do
(defn merge-entry [m e] (defn merge-entry [m e]
(setv k (get e 0) v (get e 1)) (setv k (get e 0) v (get e 1))
(if (in k m) (setv (get m k) (if (in k m)
(assoc m k (f (get m k) v)) (f (get m k) v)
(assoc m k v)) v))
m) m)
(defn merge2 [m1 m2] (defn merge2 [m1 m2]
(reduce merge-entry (.items m2) (or m1 {}))) (reduce merge-entry (.items m2) (or m1 {})))

View File

@ -20,6 +20,22 @@
~@(interleave (repeat name) rest)) ~@(interleave (repeat name) rest))
~name)) ~name))
(defmacro assoc [coll k1 v1 &rest other-kvs]
(if (odd? (len other-kvs))
(macro-error (last other-kvs)
"`assoc` takes an odd number of arguments"))
(setv c (if other-kvs
(gensym "c")
coll))
`(setv ~@(+ (if other-kvs
[c coll]
[])
#* (genexpr [`(get ~c ~k) v]
[[k v] (partition (+ (, k1 v1)
other-kvs))]))))
(defmacro with [args &rest body] (defmacro with [args &rest body]
"shorthand for nested with* loops: "shorthand for nested with* loops:
(with [x foo y bar] baz) -> (with [x foo y bar] baz) ->

View File

@ -628,12 +628,27 @@
(assoc vals "two" "three") (assoc vals "two" "three")
(assert (= (get vals "two") "three"))) (assert (= (get vals "two") "three")))
(defn test-multiassoc [] (defn test-multiassoc []
"NATIVE: test assoc multiple values" "NATIVE: test assoc multiple values"
(setv vals {"one" "two"}) (setv vals {"one" "two"})
(assoc vals "two" "three" "four" "five") (assoc vals "two" "three" "four" "five")
(assert (and (= (get vals "two") "three") (= (get vals "four") "five") (= (get vals "one") "two")))) (assert (and (= (get vals "two") "three") (= (get vals "four") "five") (= (get vals "one") "two"))))
(defn test-assoc-eval-lvalue-once []
;; https://github.com/hylang/hy/issues/1068
"`assoc` only evaluates its lvalue once"
(setv counter [])
(setv d {})
(defn f []
(.append counter 1)
d)
(assoc (f) "a" 1 "b" 2 "c" 3)
(assert (= d {"a" 1 "b" 2 "c" 3}))
(assert (= counter [1])))
(defn test-pass [] (defn test-pass []
"NATIVE: Test pass worksish" "NATIVE: Test pass worksish"
(if True (do) (do)) (if True (do) (do))