From 235244cc60e0fdc3be1bb62fca55bc0b71b340d5 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sat, 4 Nov 2023 11:18:08 -0400 Subject: [PATCH] Overhaul `hy.macroexpand` and `hy.macroexpand-1` --- NEWS.rst | 7 +-- hy/core/util.hy | 66 ++++++++++++++++------------ hy/macros.py | 6 --- tests/macros/test_macro_processor.py | 4 +- tests/native_tests/hy_misc.hy | 28 ++++++++++-- 5 files changed, 67 insertions(+), 44 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 319600a18..8f111d4b1 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -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 @@ -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) ============================= diff --git a/hy/core/util.hy b/hy/core/util.hy index fe939e477..217b6366b 100644 --- a/hy/core/util.hy +++ b/hy/core/util.hy @@ -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 `." + (_macroexpand model (or module (calling-module)) macros)) + +(defn macroexpand-1 [model [module None] [macros None]] + "Check if ``model`` is an :class:`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__ []) diff --git a/hy/macros.py b/hy/macros.py index a701c28b1..90dae35b6 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -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)( diff --git a/tests/macros/test_macro_processor.py b/tests/macros/test_macro_processor.py index 464dcca81..1e4c452e9 100644 --- a/tests/macros/test_macro_processor.py +++ b/tests/macros/test_macro_processor.py @@ -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 @@ -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 diff --git a/tests/native_tests/hy_misc.hy b/tests/native_tests/hy_misc.hy index 96a818bd4..07a03dff4 100644 --- a/tests/native_tests/hy_misc.hy +++ b/tests/native_tests/hy_misc.hy @@ -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 []