Skip to content

Commit

Permalink
Overhaul hy.macroexpand and hy.macroexpand-1
Browse files Browse the repository at this point in the history
  • Loading branch information
Kodiologist committed Nov 4, 2023
1 parent 43114b0 commit 235244c
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 44 deletions.
7 changes: 4 additions & 3 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ Breaking Changes

* `defmacro` and `require` can now define macros locally instead of
only module-wide.

* `hy.macroexpand` doesn't work with local macros (yet).

* When a macro is `require`\d from another module, that module is no
longer implicitly included when checking for further macros in
the expansion.
* `hy.eval` has been overhauled to be more like Python's `eval`. It
also has a new parameter `macros`.
* `hy.macroexpand` and `hy.macroexpand-1` have been overhauled and
generalized to include more of the features of `hy.eval`.
* `hy` now only implicitly launches a REPL if standard input is a TTY.
* `hy -i` has been overhauled to work as a flag like `python3 -i`.
* `hy2py` now requires `-m` to specify modules, and uses
Expand Down Expand Up @@ -63,6 +62,8 @@ Bug Fixes
* `nonlocal` now works for top-level `let`-bound names.
* `hy -i` with a filename now skips shebang lines.
* Implicit returns are now disabled in async generators.
* The parameter `result-ok` that was mistakenly included in the
signature of `hy.macroexpand` is now gone.

0.27.0 (released 2023-07-06)
=============================
Expand Down
66 changes: 37 additions & 29 deletions hy/core/util.hy
Original file line number Diff line number Diff line change
Expand Up @@ -84,34 +84,42 @@
(setv f (get (.stack inspect) (+ n 1) 0))
(get f.f_globals "__name__"))

(defn macroexpand [form [result-ok False]]
"Return the full macro expansion of `form`.
Examples:
::
=> (require hyrule [->])
=> (hy.macroexpand '(-> (a b) (x y)))
'(x (a b) y)
=> (hy.macroexpand '(-> (a b) (-> (c d) (e f))))
'(e (c (a b) d) f)
"
(import hy.macros)
(setv module (calling-module))
(hy.macros.macroexpand form module (HyASTCompiler module) :result-ok result-ok))

(defn macroexpand-1 [form]
"Return the single step macro expansion of `form`.
Examples:
::
=> (require hyrule [->])
=> (hy.macroexpand-1 '(-> (a b) (-> (c d) (e f))))
'(-> (a b) (c d) (e f))
"
(import hy.macros)
(setv module (calling-module))
(hy.macros.macroexpand-1 form module (HyASTCompiler module)))
(defn _macroexpand [model module macros #** kwargs]
(if (and (isinstance model hy.models.Expression) model)
(hy.macros.macroexpand
:tree model
:module module
:compiler (HyASTCompiler module :extra-macros macros)
:result-ok False
#** kwargs)
model))

(defn macroexpand [model [module None] [macros None]]
"As :hy:func:`hy.macroexpand-1`, but the expansion process is repeated until it has no effect. ::
(defmacro m [x]
(and (int x) `(m ~(- x 1))))
(print (hy.repr (hy.macroexpand-1 '(m 5))))
; => '(m 4)
(print (hy.repr (hy.macroexpand '(m 5))))
; => '0
Note that in general, macro calls in the arguments of the expression still won't expanded. To expand these, too, try Hyrule's :hy:func:`macroexpand-all <hyrule.macrotools.macroexpand-all>`."
(_macroexpand model (or module (calling-module)) macros))

(defn macroexpand-1 [model [module None] [macros None]]
"Check if ``model`` is an :class:`Expression <hy.models.Expression>` specifying a macro call. If so, expand the macro and return the expansion; otherwise, return ``model`` unchanged. ::
(defmacro m [x]
`(do ~x ~x ~x))
(print (hy.repr (hy.macroexpand-1 '(m (+= n 1)))))
; => '(do (+= n 1) (+= n 1) (+= n 1))
An exceptional case is if the macro is a core macro that returns one of Hy's internal compiler result objects instead of a real model. Then, you just get the original back, as if the macro hadn't been expanded.
The optional arguments ``module`` and ``macros`` can be provided to control where macros are looked up, as with :hy:func:`hy.eval`.
See also :hy:func:`hy.macroexpand`."
(_macroexpand model (or module (calling-module)) macros :once True))

(setv __all__ [])
6 changes: 0 additions & 6 deletions hy/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,6 @@ def macroexpand(tree, module, compiler=None, once=False, result_ok=True):
return tree


def macroexpand_1(tree, module, compiler=None):
"""Expand the toplevel macro from `tree` once, in the context of
`compiler`."""
return macroexpand(tree, module, compiler, once=True)


def rename_function(f, new_name):
"""Create a copy of a function, but with a new name."""
f = type(f)(
Expand Down
4 changes: 2 additions & 2 deletions tests/macros/test_macro_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from hy.compiler import HyASTCompiler
from hy.errors import HyMacroExpansionError
from hy.macros import macro, macroexpand, macroexpand_1
from hy.macros import macro, macroexpand
from hy.models import Expression, Float, List, String, Symbol
from hy.reader import read

Expand Down Expand Up @@ -58,6 +58,6 @@ def test_macroexpand_source_data():
ast = Expression([Symbol("when"), String("a")])
ast.start_line = 3
ast.start_column = 5
bad = macroexpand_1(ast, "hy.core.macros")
bad = macroexpand(ast, "hy.core.macros", once = True)
assert bad.start_line == 3
assert bad.start_column == 5
28 changes: 24 additions & 4 deletions tests/native_tests/hy_misc.hy
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,30 @@


(defn test-macroexpand []
(assert (= (hy.macroexpand '(mac (a b) (x y)))
'(x y (a b))))
(assert (= (hy.macroexpand '(mac (a b) (mac 5)))
'(a b 5))))
(assert (=
(hy.macroexpand '(mac (a b) (x y)))
'(x y (a b))))
(assert (=
(hy.macroexpand '(mac (a b) (mac 5)))
'(a b 5)))
(assert (=
(hy.macroexpand '(qplah "phooey") :module hy.M.tests.resources.tlib)
'[8 "phooey"]))
(assert (=
(hy.macroexpand '(chippy 1) :macros
{"chippy" (fn [&compiler x] `[~x ~x])})
'[1 1]))
; Non-Expressions just get returned as-is.
(defn f [])
(assert (is
(hy.macroexpand f)
f))
; If the macro expands to a `Result`, the user gets the original
; back instead of the `Result`.
(setv model '(+ 1 1))
(assert (is
(hy.macroexpand model)
model)))


(defmacro m-with-named-import []
Expand Down

0 comments on commit 235244c

Please sign in to comment.