Skip to content

Commit

Permalink
Merge pull request #2519 from Kodiologist/get-macro
Browse files Browse the repository at this point in the history
Add `get-macro` and remove `doc`
  • Loading branch information
Kodiologist committed Oct 22, 2023
2 parents 5439354 + 552e6df commit c08378f
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 72 deletions.
4 changes: 3 additions & 1 deletion NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ Removals
…)))` instead.
* `hy.reserved` has been removed. Use `(.keys (builtins._hy_macros))`
or Python's built-in `keyword` module instead.
* `doc` has been removed. Use `(help (get-macro foo))` or `(help
(get-macro :reader foo))` instead.

Breaking Changes
------------------------------

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

* `hy.eval`, `hy.macroexpand`, and `doc` don't work with
* `hy.eval` and `hy.macroexpand` don't work with
local macros (yet).

* When a macro is `require`\d from another module, that module is no
Expand Down
4 changes: 2 additions & 2 deletions docs/macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ Macros
Operations on macros
--------------------

The currently defined global macros can be accessed as a dictionary ``_hy_macros`` in each module. (Use ``bulitins._hy_macros``, attached to Python's usual :py:mod:`builtins` module, to see core macros.) The keys are mangled macro names and the values are the function objects that implement the macros. You can operate on this dictionary to list, add, delete, or get help on macros, but be sure to use :hy:func:`eval-and-compile` or :hy:func:`eval-when-compile` when you need the effect to happen at compile-time. ::
The currently defined global macros can be accessed as a dictionary ``_hy_macros`` in each module. (Use ``bulitins._hy_macros``, attached to Python's usual :py:mod:`builtins` module, to see core macros.) The keys are mangled macro names and the values are the function objects that implement the macros. You can operate on this dictionary to list, add, delete, or get help on macros, but be sure to use :hy:func:`eval-and-compile` or :hy:func:`eval-when-compile` when you need the effect to happen at compile-time. The core macro :hy:func:`get-macro <hy.core.macros.get-macro>` provides some syntactic sugar. ::

(defmacro m []
"This is a docstring."
`(print "Hello, world."))
(print (in "m" _hy_macros)) ; => True
(help (get _hy_macros "m"))
(help (get-macro m))
(m) ; => "Hello, world."
(eval-and-compile
(del (get _hy_macros "m")))
Expand Down
45 changes: 25 additions & 20 deletions hy/core/macros.hy
Original file line number Diff line number Diff line change
Expand Up @@ -113,26 +113,31 @@
(get _hy_reader_macros ~dispatch-key)))))


(defmacro doc [symbol]
"macro documentation
Gets help for a macro function available in this module (not a local
macro).
Use ``require`` to make other macros available.
Use ``(help foo)`` instead for help with runtime objects."
(setv symbol (str symbol))
(setv namespace
(if (= (cut symbol 1) "#")
(do (setv symbol (cut symbol 1 None))
'_hy_reader_macros)
(do (setv symbol (hy.mangle symbol))
'_hy_macros)))
(setv builtins (hy.gensym "builtins"))
`(do (import builtins :as ~builtins)
(help (or (.get ~namespace ~symbol)
(.get (. ~builtins ~namespace) ~symbol)
(raise (NameError f"macro {~symbol !r} is not defined"))))))
(defmacro get-macro [arg1 [arg2 None]]
"Get the function object used to implement a macro. This works for core macros, global (i.e., module-level) macros, and reader macros, but not local macros (yet). For regular macros, ``get-macro`` is called with one argument, a symbol or string literal, which can be premangled or not according to taste. For reader macros, this argument must be preceded by the literal keyword ``:reader`` (and note that the hash mark, ``#``, is not included in the name of the reader macro). ::
(get-macro my-macro)
(get-macro :reader my-reader-macro)
``get-macro`` expands to a :hy:func:`get <hy.pyops.get>` form on the appropriate object, such as ``_hy_macros``, selected at the time of expanding ``get-macro``. This means you can say ``(del (get-macro …))``, perhaps wrapped in :hy:func:`eval-and-compile` or :hy:func:`eval-when-compile`, to delete a macro, but it's easy to get confused by the order of evaluation and number of evaluations. For more predictable results in complex situations, use ``(del (get …))`` directly instead of ``(del (get-macro …))``."

(import builtins)
(setv [name namespace] (cond
(= arg1 ':reader)
[(str arg2) "_hy_reader_macros"]
(isinstance arg1 hy.models.Expression)
[(hy.mangle (.join "." (cut arg1 1 None))) "_hy_macros"]
True
[(hy.mangle arg1) "_hy_macros"]))
(cond
(in name (getattr &compiler.module namespace {}))
`(get ~(hy.models.Symbol namespace) ~name)
(in name (getattr builtins namespace {}))
`(get (. hy.M.builtins ~(hy.models.Symbol namespace)) ~name)
True
(raise (NameError (.format "no such {}macro: {!r}"
(if (= namespace "_hy_reader_macros") "reader " "")
name)))))


(defmacro export [#* args]
Expand Down
36 changes: 0 additions & 36 deletions tests/native_tests/doc.hy

This file was deleted.

3 changes: 3 additions & 0 deletions tests/native_tests/import.hy
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
(assert (= (tests.resources.tlib.parald 1 2 3) [9 1 2 3]))
(assert (= (tests.resources.tlib.✈ "silly") "plane silly"))
(assert (= (tests.resources.tlib.hyx_XairplaneX "foolish") "plane foolish"))
(assert (is
(get-macro tests.resources.tlib.✈)
(get _hy_macros (hy.mangle "tests.resources.tlib.✈"))))

(assert (= (TL.parald 1 2 3) [9 1 2 3]))
(assert (= (TL.✈ "silly") "plane silly"))
Expand Down
49 changes: 36 additions & 13 deletions tests/native_tests/macros_first_class.hy
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
"Tests of using macros as first-class objects: listing, creating, and
deleting them, and retrieving their docstrings."
deleting them, and retrieving their docstrings. We also test `get-macro`
(with regular, non-reader macros)."

(import
builtins)

;; * Core macros

(defn test-builtins []
(defn test-core []
(assert (in "when" (.keys builtins._hy_macros)))
(assert (not-in "global1" (.keys builtins._hy_macros)))
(assert (not-in "nonexistent" (.keys builtins._hy_macros)))

(setv s (. builtins _hy_macros ["when"] __doc__))
(assert (is
(get-macro when)
(get-macro "when")
(get builtins._hy_macros "when")))

(setv s (. (get-macro when) __doc__))
(assert s)
(assert (is (type s) str)))

;; * Global macros

;; ** Creation

; There are three ways to define a global macro:
; 1. `defmacro` in global scope
Expand All @@ -37,24 +47,37 @@ deleting them, and retrieving their docstrings."
(in (hy.mangle k) (.keys _hy_macros)))))
(assert (= (global3) "from global3"))
(assert (= (global☘) "from global☘"))
(assert (=
(. _hy_macros ["global1"] __doc__)
"global1 docstring"))
(assert (= (. (get-macro global1) __doc__) "global1 docstring"))
; https://github.com/hylang/hy/issues/1946
(assert (=
(. _hy_macros [(hy.mangle "global☘")] __doc__)
"global☘ docstring")))
(assert (= (. (get-macro global☘) __doc__) "global☘ docstring"))
(assert (= (. (get-macro hyx_globalXshamrockX) __doc__) "global☘ docstring")))

;; ** Deletion
; Try creating and then deleting a global macro.

; Try creating and and then deleting a global macro.
(defn global4 []
"from global4 function")
(setv global4-f1 (global4)) ; Calls the function
(eval-and-compile (setv (get _hy_macros "global4") (fn [&compiler]
"from global4 macro")))
(defmacro global4 []
"from global4 macro")
(setv global4-m (global4)) ; Calls the macro
(eval-and-compile (del (get _hy_macros "global4")))
(eval-when-compile (del (get-macro global4)))
(setv global4-f2 (global4)) ; Calls the function again

(defn test-global-delete []
(assert (= (global4) global4-f1 global4-f2 "from global4 function"))
(assert (= global4-m "from global4 macro")))

;; ** Shadowing a core macro
; Try overriding a core macro, then deleting the override.

(pragma :warn-on-core-shadow False)
(defmacro / [a b]
f"{(int a)}/{(int b)}")
(setv div1 (/ 1 2))
(eval-when-compile (del (get-macro /)))
(setv div2 (/ 1 2))

(defn test-global-shadowing-builtin []
(assert (= div1 "1/2"))
(assert (= div2 0.5)))
19 changes: 19 additions & 0 deletions tests/native_tests/reader_macros.hy
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@
(with [(pytest.raises PrematureEndOfInput)]
(eval-module "# _ 3")))

(defn test-get-macro []
(assert (eval-module #[[
(defreader rm1
11)
(defreader rm☘
22)
(and
(is (get-macro :reader rm1) (get _hy_reader_macros "rm1"))
(is (get-macro :reader rm☘) (get _hy_reader_macros "rm☘")))]])))

(defn test-docstring []
(assert (=
(eval-module #[[
(defreader foo
"docstring of foo"
15)
#(#foo (. (get-macro :reader foo) __doc__))]])
#(15 "docstring of foo"))))

(defn test-require-readers []
(with [module (temp-module "<test>")]
(setv it (hy.read-many #[[(require tests.resources.tlib :readers [upper!])
Expand Down

0 comments on commit c08378f

Please sign in to comment.