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:
Kodi Arfer 2017-04-21 08:07:48 -07:00 committed by Ryan Gonzalez
parent ad94343e4a
commit 4c38e2c9dd
14 changed files with 89 additions and 150 deletions

View File

@ -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

View File

@ -11,5 +11,4 @@ Contents:
interop
api
core
readermacros
internals

View File

@ -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.

View File

@ -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 +)

View File

@ -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")

View File

@ -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:

View File

@ -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))

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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