Skip to content

Commit

Permalink
Merge pull request #2530 from Kodiologist/hidden-compiler-param
Browse files Browse the repository at this point in the history
Make the magic parameter of macros optional
  • Loading branch information
Kodiologist committed Nov 13, 2023
2 parents 8eb4d6b + cefe45c commit 9fced41
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 29 deletions.
22 changes: 11 additions & 11 deletions hy/core/macros.hy
Expand Up @@ -50,7 +50,7 @@
(return panic))]]
`(if ~test (do ~@body) None))

(defmacro defreader [key #* body]
(defmacro defreader [_hy_compiler key #* body]
"Define a new reader macro.
Reader macros are expanded at read time and allow you to modify the behavior
Expand Down Expand Up @@ -89,9 +89,9 @@
See the :ref:`reader macros docs <reader-macros>` for more detailed
information on how reader macros work and are defined.
"
(when (not (isinstance &compiler.scope hy.scoping.ScopeGlobal))
(raise (&compiler._syntax-error
&compiler.this
(when (not (isinstance _hy_compiler.scope hy.scoping.ScopeGlobal))
(raise (_hy_compiler._syntax-error
_hy_compiler.this
f"Cannot define reader macro outside of global scope.")))

(when (not (isinstance key hy.models.Symbol))
Expand All @@ -113,7 +113,7 @@
(get _hy_reader_macros ~dispatch-key)))))


(defmacro get-macro [arg1 [arg2 None]]
(defmacro get-macro [_hy_compiler arg1 [arg2 None]]
"Get the function object used to implement a macro. This works for all sorts of macros: core macros, global (i.e., module-level) macros, local macros, and reader macros. For regular (non-reader) 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)
Expand All @@ -131,9 +131,9 @@
[(hy.mangle arg1) False]))
(setv namespace (if reader? "_hy_reader_macros" "_hy_macros"))
(cond
(and (not reader?) (setx local (.get (_local-macros &compiler) name)))
(and (not reader?) (setx local (.get (_local-macros _hy_compiler) name)))
local
(in name (getattr &compiler.module namespace {}))
(in name (getattr _hy_compiler.module namespace {}))
`(get ~(hy.models.Symbol namespace) ~name)
(in name (getattr builtins namespace {}))
`(get (. hy.I.builtins ~(hy.models.Symbol namespace)) ~name)
Expand All @@ -143,7 +143,7 @@
name)))))


(defmacro local-macros []
(defmacro local-macros [_hy_compiler]
#[[Expands to a dictionary mapping the mangled names of local macros to the function objects used to implement those macros. Thus, ``local-macros`` provides a rough local equivalent of ``_hy_macros``. ::
(defn f []
Expand All @@ -156,12 +156,12 @@
The equivalency is rough in the sense that ``local-macros`` returns a literal dictionary, not a preexisting object that Hy uses for resolving macro names. So, modifying the dictionary will have no effect.
See also :hy:func:`get-macro <hy.core.macros.get-macro>`.]]
(_local-macros &compiler))
(_local-macros _hy_compiler))

(defn _local_macros [&compiler]
(defn _local_macros [_hy_compiler]
(setv seen #{})
(dfor
state &compiler.local_state_stack
state _hy_compiler.local_state_stack
m (get state "macros")
:if (not-in m seen)
:do (.add seen m)
Expand Down
5 changes: 1 addition & 4 deletions hy/core/result_macros.py
Expand Up @@ -1544,10 +1544,7 @@ def E(*x): return Expression(x)
S = Symbol

compiler.warn_on_core_shadow(name)
fn_def = E(
S("fn"),
List([S("&compiler"), *expr[2]]),
*body).replace(expr)
fn_def = E(S("fn"), List(expr[2]), *body).replace(expr)
if compiler.is_in_local_state():
# We're in a local scope, so define the new macro locally.
state = compiler.local_state_stack[-1]
Expand Down
21 changes: 14 additions & 7 deletions hy/macros.py
Expand Up @@ -43,18 +43,18 @@ def pattern_macro(names, pattern, shadow=None):

def dec(fn):
def wrapper_maker(name):
def wrapper(hy_compiler, *args):
def wrapper(_hy_compiler, *args):

if shadow and any(is_unpack("iterable", x) for x in args):
# Try a shadow function call with this name instead.
return Expression(
[Expression(map(Symbol, [".", "hy", "pyops", name])), *args]
).replace(hy_compiler.this)
).replace(_hy_compiler.this)

expr = hy_compiler.this
expr = _hy_compiler.this

if py_version_required and sys.version_info < py_version_required:
raise hy_compiler._syntax_error(
raise _hy_compiler._syntax_error(
expr,
"`{}` requires Python {} or later".format(
name, ".".join(map(str, py_version_required))
Expand All @@ -64,13 +64,13 @@ def wrapper(hy_compiler, *args):
try:
parse_tree = pattern.parse(args)
except NoParseError as e:
raise hy_compiler._syntax_error(
raise _hy_compiler._syntax_error(
expr[min(e.state.pos + 1, len(expr) - 1)],
"parse error for pattern macro '{}': {}".format(
name, e.msg.replace("end of input", "end of macro call")
),
)
return fn(hy_compiler, expr, name, *parse_tree)
return fn(_hy_compiler, expr, name, *parse_tree)

return wrapper

Expand Down Expand Up @@ -414,7 +414,14 @@ def macroexpand(tree, module, compiler=None, once=False, result_ok=True):
with MacroExceptions(module, tree, compiler):
if compiler:
compiler.this = tree
obj = m(compiler, *tree[1:])
obj = m(
# If the macro's first parameter is named
# `_hy_compiler`, pass in the current compiler object
# in its place.
*([compiler]
if m.__code__.co_varnames[:1] == ('_hy_compiler',)
else []),
*tree[1:])
if isinstance(obj, (hy.compiler.Result, AST)):
return obj if result_ok else tree

Expand Down
2 changes: 1 addition & 1 deletion tests/macros/test_macro_processor.py
Expand Up @@ -8,7 +8,7 @@


@macro("test")
def tmac(ETname, *tree):
def tmac(*tree):
"""Turn an expression into a list"""
return List(tree)

Expand Down
6 changes: 3 additions & 3 deletions tests/native_tests/hy_eval.hy
Expand Up @@ -211,7 +211,7 @@

(assert (=
(hy.eval '(chippy a b) :macros (dict
:chippy (fn [&compiler arg1 arg2]
:chippy (fn [arg1 arg2]
(hy.models.Symbol (+ (str arg1) (str arg2))))))
15))

Expand All @@ -233,13 +233,13 @@
(hy.eval '(cheese))
"gorgonzola"))
(assert (=
(hy.eval '(cheese) :macros {"cheese" (fn [&compiler] "cheddar")})
(hy.eval '(cheese) :macros {"cheese" (fn [] "cheddar")})
"cheddar"))

; Or even a core macro, and with no warning.
(assert (=
(hy.eval '(+ 1 1) :macros
{(hy.mangle "+") (fn [&compiler #* args]
{(hy.mangle "+") (fn [#* args]
(.join "" (gfor x args (str (int x)))))})
"11")))

Expand Down
2 changes: 1 addition & 1 deletion tests/native_tests/hy_misc.hy
Expand Up @@ -33,7 +33,7 @@
'[8 "phooey"]))
(assert (=
(hy.macroexpand '(chippy 1) :macros
{"chippy" (fn [&compiler x] `[~x ~x])})
{"chippy" (fn [x] `[~x ~x])})
'[1 1]))
; Non-Expressions just get returned as-is.
(defn f [])
Expand Down
4 changes: 2 additions & 2 deletions tests/native_tests/macros_first_class.hy
Expand Up @@ -33,9 +33,9 @@ deleting them, and retrieving their docstrings. We also test `get-macro`
; 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]
(eval-and-compile (setv (get _hy_macros "global3") (fn []
"from global3")))
(eval-and-compile (setv (get _hy_macros (hy.mangle "global☘")) (fn [&compiler]
(eval-and-compile (setv (get _hy_macros (hy.mangle "global☘")) (fn []
"global☘ docstring"
"from global☘")))

Expand Down

0 comments on commit 9fced41

Please sign in to comment.