diff --git a/NEWS.rst b/NEWS.rst index 969f8aad1..c978e459d 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -3,13 +3,20 @@ Unreleased ============================= +Removals +------------------------------ +* `delmacro` has been removed. Use `(del (get _hy_macros (hy.mangle + …)))` instead. +* `hy.reserved` has been removed. Use `(.keys (builtins._hy_macros))` + or Python's built-in `keyword` module instead. + Breaking Changes ------------------------------ * `defmacro` and `require` can now define macros locally instead of only module-wide. - * `hy.eval`, `hy.macroexpand`, `doc`, and `delmacro` don't work with + * `hy.eval`, `hy.macroexpand`, and `doc` don't work with local macros (yet). * When a macro is `require`\d from another module, that module is no diff --git a/docs/api.rst b/docs/api.rst index ed191ff97..00530d98a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1405,9 +1405,3 @@ Python Operators .. hy:automodule:: hy.pyops :members: - -Reserved --------- - -.. hy:automodule:: hy.reserved - :members: diff --git a/docs/macros.rst b/docs/macros.rst index 73e160aef..93db2ff25 100644 --- a/docs/macros.rst +++ b/docs/macros.rst @@ -2,6 +2,23 @@ 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. :: + + (defmacro m [] + "This is a docstring." + `(print "Hello, world.")) + (print (in "m" _hy_macros)) ; => True + (help (get _hy_macros "m")) + (m) ; => "Hello, world." + (eval-and-compile + (del (get _hy_macros "m"))) + (m) ; => NameError + +``_hy_reader_macros`` is a similar dictionary for reader macros, but here, the keys aren't mangled. + .. _using-gensym: Using gensym for Safer Macros diff --git a/hy/core/macros.hy b/hy/core/macros.hy index e02dd7e0f..1e6818fdf 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -29,17 +29,16 @@ With no arguments, ``cond`` returns ``None``. With an odd number of arguments, ``cond`` raises an error." + (defn _cond [args] + (if args + `(if ~(get args 0) + ~(get args 1) + ~(_cond (cut args 2 None))) + 'None)) (if (% (len args) 2) (raise (TypeError "`cond` needs an even number of arguments")) (_cond args))) -(defn _cond [args] - (if args - `(if ~(get args 0) - ~(get args 1) - ~(_cond (cut args 2 None))) - 'None)) - (defmacro when [test #* body] #[[Shorthand for ``(if test (do …) None)``. See :hy:func:`if`. For a logically negated version, see Hyrule's :hy:func:`unless `. @@ -166,31 +165,3 @@ (if (isinstance a hy.models.List) (lfor x a (hy.models.String x)) (raise (TypeError "arguments must be keywords or lists of symbols")))))))) - -(defmacro delmacro - [#* names] - #[[Delete a macro(s) from the current module. This doesn't work on a - local macro. - :: - - => (require a-module [some-macro]) - => (some-macro) - 1 - - => (delmacro some-macro) - => (some-macro) - Traceback (most recent call last): - File "", line 1, in - (some-macro) - NameError: name 'some_macro' is not defined - - => (delmacro some-macro) - Traceback (most recent call last): - File "", line 1, in - (delmacro some-macro) - NameError: macro 'some-macro' is not defined - ]] - (let [sym (hy.gensym)] - `(eval-and-compile - (for [~sym ~(lfor name names (hy.mangle name))] - (when (in ~sym _hy_macros) (del (get _hy_macros ~sym))))))) diff --git a/hy/reserved.hy b/hy/reserved.hy deleted file mode 100644 index 6a6d4d4ff..000000000 --- a/hy/reserved.hy +++ /dev/null @@ -1,33 +0,0 @@ -;;; Get a frozenset of Hy reserved words - -(import sys keyword) - -(setv _cache None) - -(defn macros [] - "Return a frozenset of Hy's core macro names." - (frozenset (map hy.unmangle (+ - (list (.keys hy.core.result_macros._hy_macros)) - (list (.keys hy.core.macros._hy_macros)))))) - -(defn names [] - "Return a frozenset of reserved symbol names. - - The result of the first call is cached. - - The output includes all of Hy's core functions and macros, plus all - Python reserved words. All names are in unmangled form (e.g., - ``not-in`` rather than ``not_in``). - - Examples: - :: - - => (import hy.extra.reserved) - => (in \"defclass\" (hy.extra.reserved.names)) - True - " - (global _cache) - (when (is _cache None) - (setv _cache (| (macros) (frozenset (map hy.unmangle - keyword.kwlist))))) - _cache) diff --git a/tests/native_tests/macros.hy b/tests/native_tests/macros.hy index 9156d5972..6490cb707 100644 --- a/tests/native_tests/macros.hy +++ b/tests/native_tests/macros.hy @@ -168,25 +168,6 @@ (assert (in "HyWrapperError" (str excinfo.value)))) -(defmacro delete-me [] "world") - -(defn test-delmacro - [] - ;; test deletion of user defined macro - (delmacro delete-me) - (with [exc (pytest.raises NameError)] - (delete-me)) - ;; test deletion of required macros - (require tests.resources.tlib [qplah parald]) - (assert (and (qplah 1) (parald 1))) - - (delmacro qplah parald) - (with [exc (pytest.raises NameError)] - (hy.eval '(qplah))) - (with [exc (pytest.raises NameError)] - (hy.eval '(parald)))) - - (defn macro-redefinition-warning-tester [local] (for [should-warn? [True False] head ["defmacro" "require"]] (with [(if should-warn? diff --git a/tests/native_tests/macros_first_class.hy b/tests/native_tests/macros_first_class.hy new file mode 100644 index 000000000..d16f9d32c --- /dev/null +++ b/tests/native_tests/macros_first_class.hy @@ -0,0 +1,60 @@ +"Tests of using macros as first-class objects: listing, creating, and +deleting them, and retrieving their docstrings." + +(import + builtins) + + +(defn test-builtins [] + (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 s) + (assert (is (type s) str))) + + +; There are three ways to define a global macro: +; 1. `defmacro` in global scope +(defmacro global1 [] + "global1 docstring" + "from global1") +; 2. `require` in global scope +(require tests.resources.tlib [qplah :as global2]) +; 3. Manually updating `_hy_macros` +(eval-and-compile (setv (get _hy_macros "global3") (fn [&compiler] + "from global3"))) +(eval-and-compile (setv (get _hy_macros (hy.mangle "global☘")) (fn [&compiler] + "global☘ docstring" + "from global☘"))) + +(defn test-globals [] + (assert (not-in "when" (.keys _hy_macros))) + (assert (not-in "nonexistent" (.keys _hy_macros))) + (assert (all (gfor + k ["global1" "global2" "global3" "global☘"] + (in (hy.mangle k) (.keys _hy_macros))))) + (assert (= (global3) "from global3")) + (assert (= (global☘) "from global☘")) + (assert (= + (. _hy_macros ["global1"] __doc__) + "global1 docstring")) + ; https://github.com/hylang/hy/issues/1946 + (assert (= + (. _hy_macros [(hy.mangle "global☘")] __doc__) + "global☘ docstring"))) + +; 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"))) +(setv global4-m (global4)) ; Calls the macro +(eval-and-compile (del (get _hy_macros "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"))) diff --git a/tests/native_tests/reserved.hy b/tests/native_tests/reserved.hy deleted file mode 100644 index 3243b7e39..000000000 --- a/tests/native_tests/reserved.hy +++ /dev/null @@ -1,18 +0,0 @@ -(import hy.reserved [macros names]) - -(defn test-reserved-macros [] - (assert (is (type (macros)) frozenset)) - (assert (in "and" (macros))) - (assert (not-in "False" (macros))) - (assert (not-in "pass" (macros)))) - -(defn test-reserved-names [] - (assert (is (type (names)) frozenset)) - (assert (in "and" (names))) - (assert (in "False" (names))) - (assert (in "pass" (names))) - (assert (in "class" (names))) - (assert (in "defclass" (names))) - (assert (in "defmacro" (names))) - (assert (not-in "foo" (names))) - (assert (not-in "hy" (names))))