Skip to content

Commit

Permalink
Merge pull request #107 from Kodiologist/rm-walk
Browse files Browse the repository at this point in the history
Remove `walk` and overhaul `macroexpand-all`
  • Loading branch information
Kodiologist authored Dec 17, 2024
2 parents f38947f + afc435e commit 9e408fd
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 255 deletions.
8 changes: 8 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
Unreleased
======================================================

Removals
------------------------------
* `walk`, `prewalk`, and `postwalk` have been removed. Use `map-model` and
`map-hyseq` instead.

Breaking Changes
------------------------------
* `recur` is now a real object that must be imported from Hyrule when
using `loop`.
* `macroexpand-all` now uses the same parameters as `hy.macroexpand`.

New Features
------------------------------
Expand All @@ -19,6 +25,8 @@ Bug Fixes
* `map-model` now calls `as-model` only once (before its own recursion),
and it does so unconditionally.
* `loop` now works when nested.
* `macroexpand-all` no longer crashes on stub macros.
* `macroexpand-all` now recognizes macro names properly.

0.7.0 (released 2024-09-22; uses Hy ≥ 1)
======================================================
Expand Down
3 changes: 0 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ Reference
.. hy:autofunction:: assoc
.. hy:automacro:: ncut
.. hy:autofunction:: postwalk
.. hy:autofunction:: prewalk
.. hy:autotag:: s
.. hy:autofunction:: walk
``control`` — Control structures
----------------------------------------------------------------------
Expand Down
162 changes: 0 additions & 162 deletions hyrule/collections.hy
Original file line number Diff line number Diff line change
Expand Up @@ -113,168 +113,6 @@
sym))


(defn postwalk [f form]
"Performs depth-first, post-order traversal of ``form``. Calls ``f`` on
each sub-form, uses ``f`` 's return value in place of the original.
Examples:
::
=> (import hyrule.contrib.walk [postwalk])
=> (setv trail '([1 2 3] [4 [5 6 [7]]]))
=> (defn walking [x]
... (print \"Walking\" x :sep \"\\n\")
... x)
=> (postwalk walking trail)
Walking
1
Walking
2
Walking
3
Walking
hy.models.Expression([
hy.models.Integer(1),
hy.models.Integer(2),
hy.models.Integer(3)])
Walking
4
Walking
5
Walking
6
Walking
7
Walking
hy.models.Expression([
hy.models.Integer(7)])
Walking
hy.models.Expression([
hy.models.Integer(5),
hy.models.Integer(6),
hy.models.List([
hy.models.Integer(7)])])
Walking
hy.models.Expression([
hy.models.Integer(4),
hy.models.List([
hy.models.Integer(5),
hy.models.Integer(6),
hy.models.List([
hy.models.Integer(7)])])])
Walking
hy.models.Expression([
hy.models.List([
hy.models.Integer(1),
hy.models.Integer(2),
hy.models.Integer(3)]),
hy.models.List([
hy.models.Integer(4),
hy.models.List([
hy.models.Integer(5),
hy.models.Integer(6),
hy.models.List([
hy.models.Integer(7)])])])])
'([1 2 3] [4 [5 6 [7]]]))
"
(walk (partial postwalk f) f form))


(defn prewalk [f form]
"Performs depth-first, pre-order traversal of ``form``. Calls ``f`` on
each sub-form, uses ``f`` 's return value in place of the original.
Examples:
::
=> (import hyrule.contrib.walk [prewalk])
=> (setv trail '([1 2 3] [4 [5 6 [7]]]))
=> (defn walking [x]
... (print \"Walking\" x :sep \"\\n\")
... x)
=> (prewalk walking trail)
Walking
hy.models.Expression([
hy.models.List([
hy.models.Integer(1),
hy.models.Integer(2),
hy.models.Integer(3)]),
hy.models.List([
hy.models.Integer(4),
hy.models.List([
hy.models.Integer(5),
hy.models.Integer(6),
hy.models.List([
hy.models.Integer(7)])])])])
Walking
hy.models.List([
hy.models.Integer(1),
hy.models.Integer(2),
hy.models.Integer(3)])
Walking
1
Walking
2
Walking
3
Walking
hy.models.List([
hy.models.Integer(4),
hy.models.List([
hy.models.Integer(5),
hy.models.Integer(6),
hy.models.List([
hy.models.Integer(7)])])])
Walking
4
Walking
hy.models.List([
hy.models.Integer(5),
hy.models.Integer(6),
hy.models.List([
hy.models.Integer(7)])])
Walking
5
Walking
6
Walking
hy.models.List([
hy.models.Integer(7)])
Walking
7
'([1 2 3] [4 [5 6 [7]]])
"
(walk (partial prewalk f) (fn [x] x) (f form)))


(defn walk [inner outer form]
"``walk`` traverses ``form``, an arbitrary data structure. Applies
``inner`` to each element of form, building up a data structure of the
same type. Applies ``outer`` to the result.
Examples:
::
=> (import hyrule.contrib.walk [walk])
=> (setv a '(a b c d e f))
=> (walk ord (fn [x] x) a)
'(97 98 99 100 101 102)
::
=> (walk ord (fn [x] (get x 0)) a)
97
"
(cond
(isinstance form hy.models.Expression)
(outer (hy.models.Expression (map inner form)))
(or (isinstance form #(hy.models.Sequence list)))
((type form) (outer (hy.models.Expression (map inner form))))
(coll? form)
(walk inner outer (list form))
True
(outer form)))

(defn by2s [x]
#[[Returns the given iterable in pairs.
(list (by2s (range 6))) => [#(0 1) #(2 3) #(4 5)] #]]
Expand Down
4 changes: 2 additions & 2 deletions hyrule/control.hy
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(require
hyrule.macrotools [defmacro!])
(import
hyrule.collections [prewalk by2s]
hyrule.collections [by2s]
hyrule.iterables [coll?]
hyrule.misc [inc])

Expand Down Expand Up @@ -267,7 +267,7 @@
Don't forget to ``(import hyrule [recur])``. The :hy:class:`recur` object holds the arguments for the next call. When the function returns a :hy:class:`recur`, ``loop`` calls it again with the new arguments. Otherwise, ``loop`` ends and the final value is returned. Thus, what would be a nested set of recursive calls becomes a series of calls that are resolved entirely in sequence.
Note that while ``loop`` uses the same syntax as ordinary function definitions for its lambda list, all arguments other than ``#* args`` and ``#* kwargs`` must have a default value, because the function will first be called with no arguments."
Note that while ``loop`` uses the same syntax as ordinary function definitions for its lambda list, all parameters other than ``#* args`` and ``#** kwargs`` must have a default value, because the function will first be called with no arguments."

`(do
(defn ~g!f ~bindings
Expand Down
94 changes: 55 additions & 39 deletions hyrule/macrotools.hy
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
(import
hy.compiler [HyASTCompiler calling-module]
hyrule.iterables [coll? flatten]
hyrule.collections [walk])
hyrule.iterables [coll? flatten])


(defmacro defmacro-kwargs [name params #* body]
Expand Down Expand Up @@ -199,44 +198,61 @@
(setv ~~gs ~~os)
~~res)))

(defn macroexpand-all [form [ast-compiler None]]
"Recursively performs all possible macroexpansions in form, using the ``require`` context of ``module-name``.
`macroexpand-all` assumes the calling module's context if unspecified.
"
(setv quote-level 0
ast-compiler (or ast-compiler (HyASTCompiler (calling-module))))
(defn traverse [form]
(walk expand (fn [x] x) form))
(defn expand [form]
(defn macroexpand-all [model [module None] [macros None]]
"As :hy:func:`hy.macroexpand`, but with recursive descent through the input model, attempting to expand all macro calls throughout the tree::
(defmacro m [] 5)
(print (hy.repr (hy.macroexpand '(m))))
; => '5
(print (hy.repr (hy.macroexpand '(do (m)))))
; => '(do (m))
(print (hy.repr (macroexpand-all '(do (m)))))
; => '(do 5)
``macroexpand-all`` even expands macros in unquoted portions of quasiquoted forms::
(print (hy.repr (macroexpand-all '(do `[4 (m) ~(m) 6]))))
; => '(do `[4 (m) ~5 6])"

(setv quote-level 0 module (or module (hy.compiler.calling-module)))

(defn expand [m]

(when (not (and (isinstance m hy.models.Expression) m))
(return))
(setv [head #* args] m)
(when (not (isinstance head hy.models.Symbol))
(return))
(setv mhead (hy.mangle head))
(nonlocal quote-level)
;; manages quote levels
(defn +quote [[x 1]]
(nonlocal quote-level)
(setv head (get form 0))
(+= quote-level x)
(when (< quote-level 0)
(raise (TypeError "unquote outside of quasiquote")))
(setv res (traverse (cut form 1 None)))
(-= quote-level x)
`(~head ~@res))
(if (and (isinstance form hy.models.Expression) form)
(cond quote-level
(cond (in (get form 0) '[unquote unquote-splice])
(+quote -1)
(= (get form 0) 'quasiquote) (+quote)
True (traverse form))
(= (get form 0) 'quote) form
(= (get form 0) 'quasiquote) (+quote)
(= (get form 0) (hy.models.Symbol "require")) (do
(ast-compiler.compile form)
(return))
(in (get form 0) '[except unpack-mapping])
(hy.models.Expression [(get form 0) #* (traverse (cut form 1 None))])
True (traverse (hy.macros.macroexpand form ast-compiler.module ast-compiler :result-ok False)))
(if (coll? form)
(traverse form)
form)))
(expand form))
(when (and (= quote-level 0) (= mhead "quote"))
(return m))

(setv quote-adjustment None)
(cond
(and quote-level (in mhead ["unquote" "unquote_splice"]))
(setv quote-adjustment -1)
(= mhead "quasiquote")
(setv quote-adjustment 1))
(when quote-adjustment
(+= quote-level quote-adjustment))
(setv new (if
(or
quote-level
quote-adjustment
(in mhead (.split "unquote unquote_splice unpack_mapping except hyx_exceptXasteriskX finally else")))
; These stub macros would cause a crash if we tried to
; expand them.
`(~head ~@(gfor e args (map-model e expand)))
(map-hyseq
(hy.macroexpand m module macros)
(fn [e] (map-model e expand)))))
(when quote-adjustment
(-= quote-level quote-adjustment))

new)

(map-model model expand))


(defn map-model [x f]
Expand Down
18 changes: 17 additions & 1 deletion tests/test_macrotools.hy
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,23 @@
'f"{(a 1)}"))

(assert (= (get (macroexpand-all '(require-macro)) -1)
'(setv blah 1))))
'(setv blah 1)))

; Avoid crashing on stub macros like `else`.
(assert (= (macroexpand-all '(for [c "ab"] (else (foo-walk))))
'(for [c "ab"] (else 42))))

; Check quoting.
(assert (= (macroexpand-all ''(foo-walk))
''(foo-walk)))
(assert (= (macroexpand-all `(do (foo-walk)))
'(do 42)))
(assert (= (macroexpand-all '`(do (foo-walk)))
'`(do (foo-walk))))
(assert (= (macroexpand-all '`(do ~(foo-walk)))
'`(do ~42)))
(assert (= (macroexpand-all '`(do (unquote_splice (foo-walk))))
'`(do (unquote_splice 42)))))


(defn test-map-hyseq []
Expand Down
Loading

0 comments on commit 9e408fd

Please sign in to comment.