Rename reader macros to "sharp macros" (#1282)
They're not actually reader macros, since their arguments are parsed s-expressions, like a regular macro, not pre-parsed source text.
This commit is contained in:
parent
ad94343e4a
commit
4c38e2c9dd
@ -767,25 +767,39 @@ For example,
|
||||
42
|
||||
|
||||
|
||||
defreader
|
||||
---------
|
||||
defsharp
|
||||
--------
|
||||
|
||||
.. versionadded:: 0.9.12
|
||||
.. versionadded:: 0.13.0
|
||||
|
||||
``defreader`` defines a reader macro, enabling you to restructure or
|
||||
modify syntax.
|
||||
``defsharp`` defines a sharp macro. A sharp macro is a unary macro that has the
|
||||
same semantics as an ordinary macro defined with ``defmacro``, but can be
|
||||
called without parentheses and with less whitespace. The name of a sharp macro
|
||||
must be exactly one character long. It is called with the syntax ``#cFORM``,
|
||||
where ``#`` is a literal sharp sign (hence the term "sharp macro"), ``c`` is
|
||||
the name of the macro, and ``FORM`` is any form. Whitspace is forbidden between
|
||||
``#`` and ``c``. Whitespace is allowed between ``c`` and ``FORM``, but not
|
||||
required.
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
=> (defreader ^ [expr] (print expr))
|
||||
=> #^(1 2 3 4)
|
||||
(1 2 3 4)
|
||||
=> #^"Hello"
|
||||
"Hello"
|
||||
=> (defsharp ♣ [expr] `[~expr ~expr])
|
||||
<function <lambda> at 0x7f76d0271158>
|
||||
=> #♣5
|
||||
[5, 5]
|
||||
=> (setv x 0)
|
||||
=> #♣(+= x 1)
|
||||
[None, None]
|
||||
=> x
|
||||
2
|
||||
|
||||
.. seealso::
|
||||
In this example, if you used ``(defmacro ♣ ...)`` instead of ``(defsharp
|
||||
♣ ...)``, you would call the macro as ``(♣ 5)`` or ``(♣ (+= x 1))``.
|
||||
|
||||
Section :ref:`Reader Macros <reader-macros>`
|
||||
The syntax for calling sharp macros is similar to that of reader macros a la
|
||||
Common Lisp's ``SET-MACRO-CHARACTER``. In fact, before Hy 0.13.0, sharp macros
|
||||
were called "reader macros", and defined with ``defreader`` rather than
|
||||
``defsharp``. True reader macros are not (yet) implemented in Hy.
|
||||
|
||||
del
|
||||
---
|
||||
@ -1691,8 +1705,8 @@ will be 4 (``1+1 + 1+1``).
|
||||
|
||||
.. versionadded:: 0.12.0
|
||||
|
||||
The :ref:`reader macro<reader-macros>` ``#@`` can be used as a shorthand
|
||||
for ``with-decorator``. With ``#@``, the previous example becomes:
|
||||
The sharp macro ``#@`` can be used as a shorthand for ``with-decorator``. With
|
||||
``#@``, the previous example becomes:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
|
@ -11,5 +11,4 @@ Contents:
|
||||
interop
|
||||
api
|
||||
core
|
||||
readermacros
|
||||
internals
|
||||
|
@ -1,73 +0,0 @@
|
||||
.. _reader-macros:
|
||||
|
||||
.. highlight:: clj
|
||||
|
||||
=============
|
||||
Reader Macros
|
||||
=============
|
||||
|
||||
Reader macros gives Lisp the power to modify and alter syntax on the fly.
|
||||
You don't want Polish notation? A reader macro can easily do just that. Want
|
||||
Clojure's way of having a regex? Reader macros can also do this easily.
|
||||
|
||||
|
||||
Syntax
|
||||
======
|
||||
|
||||
::
|
||||
|
||||
=> (defreader ^ [expr] (print expr))
|
||||
=> #^(1 2 3 4)
|
||||
(1 2 3 4)
|
||||
=> #^"Hello"
|
||||
"Hello"
|
||||
=> #^1+2+3+4+3+2
|
||||
1+2+3+4+3+2
|
||||
|
||||
Hy has no literal for tuples. Lets say you dislike `(, ...)` and want something
|
||||
else. This is a problem reader macros are able to solve in a neat way.
|
||||
|
||||
::
|
||||
|
||||
=> (defreader t [expr] `(, ~@expr))
|
||||
=> #t(1 2 3)
|
||||
(1, 2, 3)
|
||||
|
||||
You could even do it like Clojure and have a literal for regular expressions!
|
||||
|
||||
::
|
||||
|
||||
=> (import re)
|
||||
=> (defreader r [expr] `(re.compile ~expr))
|
||||
=> #r".*"
|
||||
<_sre.SRE_Pattern object at 0xcv7713ph15#>
|
||||
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
``defreader`` takes a single character as symbol name for the reader macro;
|
||||
anything longer will return an error. Implementation-wise, ``defreader``
|
||||
expands into a lambda covered with a decorator. This decorator saves the
|
||||
lambda in a dictionary with its module name and symbol.
|
||||
|
||||
::
|
||||
|
||||
=> (defreader ^ [expr] (print expr))
|
||||
;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr)))
|
||||
|
||||
``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol
|
||||
and expression is passed to the correct function.
|
||||
|
||||
::
|
||||
|
||||
=> #^()
|
||||
;=> (dispatch_reader_macro ^ ())
|
||||
=> #^"Hello"
|
||||
"Hello"
|
||||
|
||||
|
||||
.. warning::
|
||||
Because of a limitation in Hy's lexer and parser, reader macros can't
|
||||
redefine defined syntax such as ``()[]{}``. This will most likely be
|
||||
addressed in the future.
|
@ -581,14 +581,14 @@ elements, so by the time program started executing, it actually reads:
|
||||
|
||||
(+ 1 2 3)
|
||||
|
||||
Sometimes it's nice to have a very short name for a macro that doesn't take much
|
||||
space or use extra parentheses. Reader macros can be pretty useful in these
|
||||
situations (and since Hy operates well with unicode, we aren't running out of
|
||||
characters that soon):
|
||||
Sometimes it's nice to be able to call a one-parameter macro without
|
||||
parentheses. Sharp macros allow this. The name of a sharp macro must be only
|
||||
one character long, but since Hy operates well with Unicode, we aren't running
|
||||
out of characters that soon:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
=> (defreader ↻ [code]
|
||||
=> (defsharp ↻ [code]
|
||||
... (setv op (last code) params (list (butlast code)))
|
||||
... `(~op ~@params))
|
||||
=> #↻(1 2 3 +)
|
||||
|
@ -34,7 +34,7 @@ from hy.lex.parser import hy_symbol_mangle
|
||||
import hy.macros
|
||||
from hy._compat import (
|
||||
str_type, bytes_type, long_type, PY27, PY33, PY3, PY34, PY35, raise_empty)
|
||||
from hy.macros import require, macroexpand, reader_macroexpand
|
||||
from hy.macros import require, macroexpand, sharp_macroexpand
|
||||
import hy.importer
|
||||
|
||||
import traceback
|
||||
@ -2544,21 +2544,20 @@ class HyASTCompiler(object):
|
||||
|
||||
return ret
|
||||
|
||||
@builds("defreader")
|
||||
@builds("defsharp")
|
||||
@checkargs(min=2)
|
||||
def compile_reader(self, expression):
|
||||
def compile_sharp_macro(self, expression):
|
||||
expression.pop(0)
|
||||
name = expression.pop(0)
|
||||
NOT_READERS = [":", "&"]
|
||||
if name in NOT_READERS or len(name) > 1:
|
||||
raise NameError("%s can't be used as a macro reader symbol" % name)
|
||||
if name == ":" or name == "&" or len(name) > 1:
|
||||
raise NameError("%s can't be used as a sharp macro name" % name)
|
||||
if not isinstance(name, HySymbol) and not isinstance(name, HyString):
|
||||
raise HyTypeError(name,
|
||||
("received a `%s' instead of a symbol "
|
||||
"for reader macro name" % type(name).__name__))
|
||||
"for sharp macro name" % type(name).__name__))
|
||||
name = HyString(name).replace(name)
|
||||
new_expression = HyExpression([
|
||||
HyExpression([HySymbol("hy.macros.reader"), name]),
|
||||
HyExpression([HySymbol("hy.macros.sharp"), name]),
|
||||
HyExpression([HySymbol("fn")] + expression),
|
||||
]).replace(expression)
|
||||
|
||||
@ -2566,18 +2565,18 @@ class HyASTCompiler(object):
|
||||
|
||||
return ret
|
||||
|
||||
@builds("dispatch_reader_macro")
|
||||
@builds("dispatch_sharp_macro")
|
||||
@checkargs(exact=2)
|
||||
def compile_dispatch_reader_macro(self, expression):
|
||||
expression.pop(0) # dispatch-reader-macro
|
||||
def compile_dispatch_sharp_macro(self, expression):
|
||||
expression.pop(0) # dispatch-sharp-macro
|
||||
str_char = expression.pop(0)
|
||||
if not type(str_char) == HyString:
|
||||
raise HyTypeError(
|
||||
str_char,
|
||||
"Trying to expand a reader macro using `{0}' instead "
|
||||
"Trying to expand a sharp macro using `{0}' instead "
|
||||
"of string".format(type(str_char).__name__),
|
||||
)
|
||||
expr = reader_macroexpand(str_char, expression.pop(0), self)
|
||||
expr = sharp_macroexpand(str_char, expression.pop(0), self)
|
||||
return self.compile(expr)
|
||||
|
||||
@builds("eval_and_compile")
|
||||
|
@ -63,11 +63,11 @@ class Completer(object):
|
||||
builtins.__dict__,
|
||||
hy.macros._hy_macros[None],
|
||||
namespace]
|
||||
self.reader_path = [hy.macros._hy_reader[None]]
|
||||
self.sharp_path = [hy.macros._hy_sharp[None]]
|
||||
if '__name__' in namespace:
|
||||
module_name = namespace['__name__']
|
||||
self.path.append(hy.macros._hy_macros[module_name])
|
||||
self.reader_path.append(hy.macros._hy_reader[module_name])
|
||||
self.sharp_path.append(hy.macros._hy_sharp[module_name])
|
||||
|
||||
def attr_matches(self, text):
|
||||
# Borrowed from IPython's completer
|
||||
@ -104,10 +104,10 @@ class Completer(object):
|
||||
matches.append(k)
|
||||
return matches
|
||||
|
||||
def reader_matches(self, text):
|
||||
def sharp_matches(self, text):
|
||||
text = text[1:]
|
||||
matches = []
|
||||
for p in self.reader_path:
|
||||
for p in self.sharp_path:
|
||||
for k in p.keys():
|
||||
if isinstance(k, string_types):
|
||||
if k.startswith(text):
|
||||
@ -116,7 +116,7 @@ class Completer(object):
|
||||
|
||||
def complete(self, text, state):
|
||||
if text.startswith("#"):
|
||||
matches = self.reader_matches(text)
|
||||
matches = self.sharp_matches(text)
|
||||
elif "." in text:
|
||||
matches = self.attr_matches(text)
|
||||
else:
|
||||
|
@ -250,7 +250,7 @@
|
||||
(sys.exit ~retval))))
|
||||
|
||||
|
||||
(defreader @ [expr]
|
||||
(defsharp @ [expr]
|
||||
(setv decorators (cut expr None -1)
|
||||
fndef (get expr -1))
|
||||
`(with-decorator ~@decorators ~fndef))
|
||||
|
@ -41,7 +41,7 @@ lg.add('QUASIQUOTE', r'`%s' % end_quote)
|
||||
lg.add('UNQUOTESPLICE', r'~@%s' % end_quote)
|
||||
lg.add('UNQUOTE', r'~%s' % end_quote)
|
||||
lg.add('HASHBANG', r'#!.*[^\r\n]')
|
||||
lg.add('HASHREADER', r'#[^{]')
|
||||
lg.add('HASHOTHER', r'#[^{]')
|
||||
|
||||
# A regexp which matches incomplete strings, used to support
|
||||
# multi-line strings in the interpreter
|
||||
|
@ -213,13 +213,13 @@ def term_unquote_splice(p):
|
||||
return HyExpression([HySymbol("unquote_splice"), p[1]])
|
||||
|
||||
|
||||
@pg.production("term : HASHREADER term")
|
||||
@pg.production("term : HASHOTHER term")
|
||||
@set_quote_boundaries
|
||||
def hash_reader(p):
|
||||
def hash_other(p):
|
||||
st = p[0].getstr()[1]
|
||||
str_object = HyString(st)
|
||||
expr = p[1]
|
||||
return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr])
|
||||
return HyExpression([HySymbol("dispatch_sharp_macro"), str_object, expr])
|
||||
|
||||
|
||||
@pg.production("set : HLCURLY list_contents RCURLY")
|
||||
|
26
hy/macros.py
26
hy/macros.py
@ -34,7 +34,7 @@ EXTRA_MACROS = [
|
||||
]
|
||||
|
||||
_hy_macros = defaultdict(dict)
|
||||
_hy_reader = defaultdict(dict)
|
||||
_hy_sharp = defaultdict(dict)
|
||||
|
||||
|
||||
def macro(name):
|
||||
@ -66,8 +66,8 @@ def macro(name):
|
||||
return _
|
||||
|
||||
|
||||
def reader(name):
|
||||
"""Decorator to define a reader macro called `name`.
|
||||
def sharp(name):
|
||||
"""Decorator to define a sharp macro called `name`.
|
||||
|
||||
This stores the macro `name` in the namespace for the module where it is
|
||||
defined.
|
||||
@ -75,14 +75,14 @@ def reader(name):
|
||||
If the module where it is defined is in `hy.core`, then the macro is stored
|
||||
in the default `None` namespace.
|
||||
|
||||
This function is called from the `defreader` special form in the compiler.
|
||||
This function is called from the `defsharp` special form in the compiler.
|
||||
|
||||
"""
|
||||
def _(fn):
|
||||
module_name = fn.__module__
|
||||
if module_name.startswith("hy.core"):
|
||||
module_name = None
|
||||
_hy_reader[module_name][name] = fn
|
||||
_hy_sharp[module_name][name] = fn
|
||||
|
||||
return fn
|
||||
return _
|
||||
@ -106,7 +106,7 @@ def require(source_module, target_module,
|
||||
if prefix:
|
||||
prefix += "."
|
||||
|
||||
for d in _hy_macros, _hy_reader:
|
||||
for d in _hy_macros, _hy_sharp:
|
||||
for name, macro in d[source_module].items():
|
||||
seen_names.add(name)
|
||||
if all_macros:
|
||||
@ -226,19 +226,19 @@ def macroexpand_1(tree, compiler):
|
||||
return tree
|
||||
|
||||
|
||||
def reader_macroexpand(char, tree, compiler):
|
||||
"""Expand the reader macro "char" with argument `tree`."""
|
||||
def sharp_macroexpand(char, tree, compiler):
|
||||
"""Expand the sharp macro "char" with argument `tree`."""
|
||||
load_macros(compiler.module_name)
|
||||
|
||||
reader_macro = _hy_reader[compiler.module_name].get(char)
|
||||
if reader_macro is None:
|
||||
sharp_macro = _hy_sharp[compiler.module_name].get(char)
|
||||
if sharp_macro is None:
|
||||
try:
|
||||
reader_macro = _hy_reader[None][char]
|
||||
sharp_macro = _hy_sharp[None][char]
|
||||
except KeyError:
|
||||
raise HyTypeError(
|
||||
char,
|
||||
"`{0}' is not a defined reader macro.".format(char)
|
||||
"`{0}' is not a defined sharp macro.".format(char)
|
||||
)
|
||||
|
||||
expr = reader_macro(tree)
|
||||
expr = sharp_macro(tree)
|
||||
return replace_hy_obj(wrap_value(expr), tree)
|
||||
|
@ -11,7 +11,7 @@ from .native_tests.unless import * # noqa
|
||||
from .native_tests.when import * # noqa
|
||||
from .native_tests.with_decorator import * # noqa
|
||||
from .native_tests.core import * # noqa
|
||||
from .native_tests.reader_macros import * # noqa
|
||||
from .native_tests.sharp_macros import * # noqa
|
||||
from .native_tests.operators import * # noqa
|
||||
from .native_tests.with_test import * # noqa
|
||||
from .native_tests.extra.anaphoric import * # noqa
|
||||
|
@ -3,10 +3,10 @@ from hy.compiler import HyTypeError, HyASTCompiler
|
||||
from hy.lex import tokenize
|
||||
|
||||
|
||||
def test_reader_macro_error():
|
||||
def test_sharp_macro_error():
|
||||
"""Check if we get correct error with wrong dispatch character"""
|
||||
try:
|
||||
macroexpand(tokenize("(dispatch_reader_macro '- '())")[0],
|
||||
macroexpand(tokenize("(dispatch_sharp_macro '- '())")[0],
|
||||
HyASTCompiler(__name__))
|
||||
except HyTypeError as e:
|
||||
assert "with the character `-`" in str(e)
|
@ -1,34 +1,34 @@
|
||||
(import [functools [wraps]])
|
||||
|
||||
|
||||
(defn test-reader-macro []
|
||||
"Test a basic reader macro"
|
||||
(defreader ^ [expr]
|
||||
(defn test-sharp-macro []
|
||||
"Test a basic sharp macro"
|
||||
(defsharp ^ [expr]
|
||||
expr)
|
||||
|
||||
(assert (= #^"works" "works")))
|
||||
|
||||
|
||||
(defn test-reader-macro-expr []
|
||||
(defn test-sharp-macro-expr []
|
||||
"Test basic exprs like lists and arrays"
|
||||
(defreader n [expr]
|
||||
(defsharp n [expr]
|
||||
(get expr 1))
|
||||
|
||||
(assert (= #n[1 2] 2))
|
||||
(assert (= #n(1 2) 2)))
|
||||
|
||||
|
||||
(defn test-reader-macro-override []
|
||||
(defn test-sharp-macro-override []
|
||||
"Test if we can override function symbols"
|
||||
(defreader + [n]
|
||||
(defsharp + [n]
|
||||
(+ n 1))
|
||||
|
||||
(assert (= #+2 3)))
|
||||
|
||||
|
||||
(defn test-reader-macros-macros []
|
||||
"Test if defreader is actually a macro"
|
||||
(defreader t [expr]
|
||||
(defn test-sharp-macros-macros []
|
||||
"Test if defsharp is actually a macro"
|
||||
(defsharp t [expr]
|
||||
`(, ~@expr))
|
||||
|
||||
(def a #t[1 2 3])
|
||||
@ -37,16 +37,16 @@
|
||||
(assert (= (, 1 2 3) a)))
|
||||
|
||||
|
||||
(defn test-reader-macro-string-name []
|
||||
"Test if defreader accepts a string as a macro name."
|
||||
(defn test-sharp-macro-string-name []
|
||||
"Test if defsharp accepts a string as a macro name."
|
||||
|
||||
(defreader "." [expr]
|
||||
(defsharp "." [expr]
|
||||
expr)
|
||||
|
||||
(assert (= #."works" "works")))
|
||||
|
||||
|
||||
(defn test-builtin-decorator-reader []
|
||||
(defn test-builtin-decorator-sharp []
|
||||
(defn increment-arguments [func]
|
||||
"Increments each argument passed to the decorated function."
|
||||
((wraps func)
|
||||
@ -68,7 +68,7 @@
|
||||
(assert (= "foo" (. foo --name--)))
|
||||
(assert (= "Bar." (. foo --doc--)))
|
||||
|
||||
;; We can use the #@ reader macro to apply more than one decorator
|
||||
;; We can use the #@ sharp macro to apply more than one decorator
|
||||
#@(increment-arguments
|
||||
increment-arguments
|
||||
(defn double-foo [&rest args &kwargs kwargs]
|
@ -320,10 +320,10 @@ def test_complex():
|
||||
assert entry == HySymbol("j")
|
||||
|
||||
|
||||
def test_reader_macro():
|
||||
"""Ensure reader macros are handles properly"""
|
||||
def test_sharp_macro():
|
||||
"""Ensure sharp macros are handled properly"""
|
||||
entry = tokenize("#^()")
|
||||
assert entry[0][0] == HySymbol("dispatch_reader_macro")
|
||||
assert entry[0][0] == HySymbol("dispatch_sharp_macro")
|
||||
assert entry[0][1] == HyString("^")
|
||||
assert len(entry[0]) == 3
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user