Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify tag macros #1971

Merged
merged 14 commits into from
Feb 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,28 @@ Removals
------------------------------
* `name` function has been removed from core.
Use `(. :keyword name)` and/or explicit calls to `unmangle` instead.
* `deftag` has been removed. Instead of `(deftag foo …)`,
say `(defmacro "#foo" …)`.
* `#doc` has been removed. Instead of `#doc @`, say `(doc "#@")`.
* `__tags__` has been removed. Tag macros are now tracked in
`__macros__`.

Breaking Changes
------------------------------
* f-strings are now parsed as a separate `HyFString` node,
which is a collection of `HyString` and `HyFComponent` nodes.
* Calling a `HyKeyword` now looks up by its (string) name instead by its self.
`(:key obj)` is now equivalent to `(get obj (. :key name))`.
* To require a tag macro `foo`, instead of `(require [module [foo]])`,
you must now say `(require [module ["#foo"]])`.

New Features
------------------------------
* New contrib module `destructure` for Clojure-style destructuring.
* Location of history file now configurable via environment variable `HY_HISTORY`
* Added handling for "=" syntax in f-strings.
* Repl init scripts with `HYSTARTUP` env var
* `defmacro` and `require` can now take macro names as string literals.

Bug Fixes
------------------------------
Expand Down
1 change: 0 additions & 1 deletion docs/cheatsheet.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
"hy.core.macros.comment",
"hy.core.macros.doc",
"hy.core.bootstrap.defmacro",
"hy.core.bootstrap.deftag",
"hy.core.bootstrap.if",
"hy.core.bootstrap.defn/a",
"hy.core.language.calling-module",
Expand Down
2 changes: 1 addition & 1 deletion docs/style-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ Comments

Prefer docstrings to comments where applicable---in ``fn``, ``defclass``,
at the top of the module, and in any other macros derived from these that can take a docstring
(e.g. ``defmacro/g!``, ``deftag``, ``defn``).
(e.g. ``defmacro/g!``, ``defn``).

Docstrings contents follow the same conventions as Python.

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ parentheses. Tag macros allow this. The name of a tag macro is often just one
character long, but since Hy allows most Unicode characters in the name of a
macro (or ordinary variable), you won't out of characters soon. ::

=> (deftag ↻ [code]
=> (defmacro "#↻" [code]
... (setv op (last code) params (list (butlast code)))
... `(~op ~@params))
=> #↻(1 2 3 +)
Expand Down
26 changes: 11 additions & 15 deletions hy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from hy.lex import mangle, unmangle, hy_parse, parse_one_thing, LexException

from hy._compat import (PY38, reraise)
from hy.macros import require, load_macros, macroexpand, tag_macroexpand
from hy.macros import require, load_macros, macroexpand

import hy.core

Expand Down Expand Up @@ -379,10 +379,9 @@ def __init__(self, module, filename=None, source=None):
self.filename = filename
self.source = source

# Hy expects these to be present, so we prep the module for Hy
# Hy expects this to be present, so we prep the module for Hy
# compilation.
self.module.__dict__.setdefault('__macros__', {})
self.module.__dict__.setdefault('__tags__', {})

self.can_use_stdlib = not self.module_name.startswith("hy.core")

Expand Down Expand Up @@ -1134,12 +1133,16 @@ def compile_unary_operator(self, expr, root, arg):
return operand + asty.UnaryOp(
expr, op=ops[root](), operand=operand.force_expr)

_symn = some(lambda x: isinstance(x, HySymbol) and "." not in x)
def _importlike(*name_types):
name = some(lambda x: isinstance(x, name_types) and "." not in x)
scauligi marked this conversation as resolved.
Show resolved Hide resolved
return [many(
SYM |
brackets(SYM, sym(":as"), name) |
brackets(SYM, brackets(many(
name + maybe(sym(":as") + name)))))]

@special(["import", "require"], [many(
SYM |
brackets(SYM, sym(":as"), _symn) |
brackets(SYM, brackets(many(_symn + maybe(sym(":as") + _symn)))))])
@special("import", _importlike(HySymbol))
@special("require", _importlike(HySymbol, HyString))
def compile_import_or_require(self, expr, root, entries):
ret = Result()

Expand Down Expand Up @@ -1673,13 +1676,6 @@ def _rewire_init(self, expr):
return (HyExpression([HySymbol("setv")] + new_args + decls)
.replace(expr))

@special("dispatch-tag-macro", [STR, FORM])
def compile_dispatch_tag_macro(self, expr, root, tag, arg):
return self.compile(tag_macroexpand(
HyString(mangle(tag)).replace(tag),
arg,
self.module))

@special(["eval-and-compile", "eval-when-compile"], [many(FORM)])
def compile_eval_and_compile(self, expr, root, body):
new_expr = HyExpression([HySymbol("do").replace(expr[0])]).replace(expr)
Expand Down
18 changes: 1 addition & 17 deletions hy/completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,9 @@ def __init__(self, namespace={}):
builtins.__dict__,
namespace]

self.tag_path = []

namespace.setdefault('__macros__', {})
namespace.setdefault('__tags__', {})

self.path.append(namespace['__macros__'])
self.tag_path.append(namespace['__tags__'])

def attr_matches(self, text):
# Borrowed from IPython's completer
Expand Down Expand Up @@ -84,20 +80,8 @@ def global_matches(self, text):
matches.append(k)
return matches

def tag_matches(self, text):
text = text[1:]
matches = []
for p in self.tag_path:
for k in p.keys():
if isinstance(k, str):
if k.startswith(text):
matches.append("#{}".format(k))
return matches

def complete(self, text, state):
if text.startswith("#"):
matches = self.tag_matches(text)
elif "." in text:
if "." in text:
matches = self.attr_matches(text)
else:
matches = self.global_matches(text)
Expand Down
71 changes: 22 additions & 49 deletions hy/core/bootstrap.hy
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
(import hy)
((hy.macros.macro "defmacro")
(fn [&name macro-name lambda-list &rest body]
"the defmacro macro
#[[the defmacro macro
``defmacro`` is used to define macros. The general format is
``(defmacro name [parameters] expr)``.

Expand All @@ -31,13 +31,31 @@

=> (infix (1 + 1))
2
"
(if* (not (isinstance macro-name hy.models.HySymbol))

The name of the macro can be given as a string literal instead of a symbol. If the name starts with `#`, the macro can be called on a single argument without parentheses; such a macro is called a tag macro.

::

=> (defmacro "#x2" [form]
... `(do ~form ~form))

::

=> (setv foo 1)
=> #x2 (+= foo 1)
=> foo
3
]]
(if* (not (isinstance macro-name (, hy.models.HySymbol hy.models.HyString)))
(raise
(hy.errors.HyTypeError
(% "received a `%s' instead of a symbol for macro name"
(% "received a `%s' instead of a symbol or string for macro name"
(. (type macro-name) __name__))
None --file-- None)))
(if* (in "." macro-name)
(raise (hy.errors.HyTypeError
"periods are not allowed in macro names"
None --file-- None)))
(for [kw '[&kwonly &kwargs]]
(if* (in kw lambda-list)
(raise (hy.errors.HyTypeError (% "macros cannot use %s"
Expand Down Expand Up @@ -91,51 +109,6 @@
~(get args 1)
(if ~@(cut args 2))))))

(defmacro deftag [tag-name lambda-list &rest body]
"Defines a tag macro.

.. versionadded:: 0.13.0

``deftag`` defines a tag macro. A tag macro is a unary macro that has the
same semantics as an ordinary macro defined with ``defmacro``. It is called with
the syntax ``#tag FORM``, where ``tag`` is the name of the macro, and ``FORM``
is any form. The ``tag`` is often only one character, but it can be any symbol.

Examples:
In this example, if you used ``(defmacro ♣ ...)`` instead of ``(deftag
♣ ...)``, you would call the macro as ``(♣ 5)`` or ``(♣ (+= x 1))``.

The syntax for calling tag macros is similar to that of reader macros a la
Common Lisp's ``SET-MACRO-CHARACTER``. In fact, before Hy 0.13.0, tag macros
were called \"reader macros\", and defined with ``defreader`` rather than
``deftag``. True reader macros are not (yet) implemented in Hy::

=> (deftag ♣ [expr] `[~expr ~expr])
=> #♣ 5
[5, 5]
=> (setv x 0)
=> #♣(+= x 1)
[None, None]
=> x
2
"
(import hy.models)
(if (and (not (isinstance tag-name hy.models.HySymbol))
(not (isinstance tag-name hy.models.HyString)))
(raise (hy.errors.HyTypeError
(% "received a `%s' instead of a symbol for tag macro name"
(. (type tag-name) --name--))
tag-name --file-- None)))
(if (or (= tag-name ":")
(= tag-name "&"))
(raise (hy.errors.HyNameError (% "%s can't be used as a tag macro name" tag-name))))
(setv tag-name (.replace (hy.models.HyString tag-name)
tag-name))
`(eval-and-compile
(import hy)
((hy.macros.tag ~tag-name)
(fn ~lambda-list ~@body))))

(defmacro macro-error [expression reason &optional [filename '--name--]]
`(raise (hy.errors.HyMacroExpansionError ~reason ~filename ~expression None)))

Expand Down
10 changes: 1 addition & 9 deletions hy/core/macros.hy
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@
(sys.exit ~retval))))


(deftag @ [expr]
(defmacro "#@" [expr]
"with-decorator tag macro"
(if (empty? expr)
(macro-error expr "missing function argument"))
Expand Down Expand Up @@ -731,13 +731,5 @@
Gets help for a macro function available in this module.
Use ``require`` to make other macros available.

Use ``#doc foo`` instead for help with tag macro ``#foo``.
Use ``(help foo)`` instead for help with runtime objects."
`(help (.get __macros__ (mangle '~symbol) None)))


(deftag doc [symbol]
"tag macro documentation

Gets help for a tag macro function available in this module."
`(help (.get __tags__ (mangle '~symbol) None)))
2 changes: 1 addition & 1 deletion hy/extra/anaphoric.hy
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ variable name, as in ``(print \"My favorite Stephen King book is\" 'it)``."
~acc))


(deftag % [expr]
(defmacro "#%" [expr]
"Makes an expression into a function with an implicit ``%`` parameter list.

A ``%i`` symbol designates the (1-based) *i* th parameter (such as ``%3``).
Expand Down
5 changes: 1 addition & 4 deletions hy/lex/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,7 @@ def term_hashstars(state, p):
@set_quote_boundaries
def hash_other(state, p):
# p == [(Token('HASHOTHER', '#foo'), bar)]
st = p[0].getstr()[1:]
str_object = HyString(st)
expr = p[1]
return HyExpression([HySymbol("dispatch-tag-macro"), str_object, expr])
return HyExpression([HySymbol(p[0].getstr()), p[1]])


@pg.production("set : HLCURLY list_contents RCURLY")
Expand Down
Loading