Hy reader macros #377
Added first iteration of reader macros Refactored defmacro and defreader Added test inn hy/tests/lex/test_lex.py Added new test in hy/tests/native/tests Added new test in hy/tests/macros. changed the error given in the dispatch macro and added some handling for missing symbol and invalid characters
This commit is contained in:
parent
1a701d4dc4
commit
c9fdd40c9f
@ -36,6 +36,7 @@ from hy.models.float import HyFloat
|
||||
from hy.models.list import HyList
|
||||
from hy.models.dict import HyDict
|
||||
|
||||
import hy.macros
|
||||
from hy.macros import require, macroexpand
|
||||
from hy._compat import str_type, long_type
|
||||
import hy.importer
|
||||
@ -1753,6 +1754,19 @@ class HyASTCompiler(object):
|
||||
bases=bases_expr,
|
||||
body=body.stmts)
|
||||
|
||||
def _compile_time_hack(self, expression):
|
||||
"""Compile-time hack: we want to get our new macro now
|
||||
We must provide __name__ in the namespace to make the Python
|
||||
compiler set the __module__ attribute of the macro function."""
|
||||
hy.importer.hy_eval(expression,
|
||||
compile_time_ns(self.module_name),
|
||||
self.module_name)
|
||||
|
||||
# We really want to have a `hy` import to get hy.macro in
|
||||
ret = self.compile(expression)
|
||||
ret.add_imports('hy', [None])
|
||||
return ret
|
||||
|
||||
@builds("defmacro")
|
||||
@checkargs(min=1)
|
||||
def compile_macro(self, expression):
|
||||
@ -1768,16 +1782,30 @@ class HyASTCompiler(object):
|
||||
HyExpression([HySymbol("fn")] + expression),
|
||||
]).replace(expression)
|
||||
|
||||
# Compile-time hack: we want to get our new macro now
|
||||
# We must provide __name__ in the namespace to make the Python
|
||||
# compiler set the __module__ attribute of the macro function.
|
||||
hy.importer.hy_eval(new_expression,
|
||||
compile_time_ns(self.module_name),
|
||||
self.module_name)
|
||||
ret = self._compile_time_hack(new_expression)
|
||||
|
||||
# We really want to have a `hy` import to get hy.macro in
|
||||
ret = self.compile(new_expression)
|
||||
ret.add_imports('hy', [None])
|
||||
return ret
|
||||
|
||||
@builds("defreader")
|
||||
@checkargs(min=2, max=3)
|
||||
def compile_reader(self, expression):
|
||||
expression.pop(0)
|
||||
name = expression.pop(0)
|
||||
NOT_READERS = [":", "&"]
|
||||
if name in NOT_READERS:
|
||||
raise NameError("%s can't be used as a macro reader symbol" % name)
|
||||
if not isinstance(name, HySymbol):
|
||||
raise HyTypeError(name,
|
||||
("received a `%s' instead of a symbol "
|
||||
"for reader macro name" % type(name).__name__))
|
||||
name = HyString(name).replace(name)
|
||||
new_expression = HyExpression([
|
||||
HySymbol("with_decorator"),
|
||||
HyExpression([HySymbol("hy.macros.reader"), name]),
|
||||
HyExpression([HySymbol("fn")] + expression),
|
||||
]).replace(expression)
|
||||
|
||||
ret = self._compile_time_hack(new_expression)
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -25,7 +25,6 @@
|
||||
;;; These macros form the hy language
|
||||
;;; They are automatically required in every module, except inside hy.core
|
||||
|
||||
|
||||
(defmacro for [args &rest body]
|
||||
"shorthand for nested foreach loops:
|
||||
(for [x foo y bar] baz) ->
|
||||
@ -144,3 +143,13 @@
|
||||
(setv -args (cdr (car -args))))
|
||||
|
||||
`(apply ~-fun [~@-args] (dict (sum ~-okwargs [])))))
|
||||
|
||||
|
||||
(defmacro dispatch-reader-macro [char &rest body]
|
||||
"Dispatch a reader macro based on the character"
|
||||
(import [hy.macros])
|
||||
(setv str_char (get char 1))
|
||||
(if (not (in str_char hy.macros._hy_reader_chars))
|
||||
(raise (hy.compiler.HyTypeError char (.format "There is no reader macro with the character `{0}`" str_char))))
|
||||
`(do (import [hy.macros [_hy_reader]])
|
||||
((get (get _hy_reader --name--) ~char) ~(get body 0))))
|
||||
|
@ -40,6 +40,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('STRING', r'''(?x)
|
||||
|
@ -150,6 +150,15 @@ def term_unquote_splice(p):
|
||||
return HyExpression([HySymbol("unquote_splice"), p[1]])
|
||||
|
||||
|
||||
@pg.production("term : HASHREADER term")
|
||||
@set_quote_boundaries
|
||||
def hash_reader(p):
|
||||
st = p[0].getstr()[1]
|
||||
str_object = HyExpression([HySymbol("quote"), HyString(st)])
|
||||
expr = HyExpression([HySymbol("quote"), p[1]])
|
||||
return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr])
|
||||
|
||||
|
||||
@pg.production("dict : LCURLY list_contents RCURLY")
|
||||
@set_boundaries
|
||||
def t_dict(p):
|
||||
|
31
hy/macros.py
31
hy/macros.py
@ -40,6 +40,8 @@ EXTRA_MACROS = [
|
||||
]
|
||||
|
||||
_hy_macros = defaultdict(dict)
|
||||
_hy_reader = defaultdict(dict)
|
||||
_hy_reader_chars = set()
|
||||
|
||||
|
||||
def macro(name):
|
||||
@ -63,6 +65,30 @@ def macro(name):
|
||||
return _
|
||||
|
||||
|
||||
def reader(name):
|
||||
"""Decorator to define a macro called `name`.
|
||||
|
||||
This stores the macro `name` in the namespace for the module where it is
|
||||
defined.
|
||||
|
||||
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 `defmacro` 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
|
||||
|
||||
# Ugly hack to get some error handling
|
||||
_hy_reader_chars.add(name)
|
||||
return fn
|
||||
return _
|
||||
|
||||
|
||||
def require(source_module, target_module):
|
||||
"""Load the macros from `source_module` in the namespace of
|
||||
`target_module`.
|
||||
@ -75,6 +101,11 @@ def require(source_module, target_module):
|
||||
for name, macro in macros.items():
|
||||
refs[name] = macro
|
||||
|
||||
readers = _hy_reader[source_module]
|
||||
reader_refs = _hy_reader[target_module]
|
||||
for name, reader in readers.items():
|
||||
reader_refs[name] = reader
|
||||
|
||||
|
||||
# type -> wrapping function mapping for _wrap_value
|
||||
_wrappers = {
|
||||
|
@ -11,3 +11,4 @@ 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
|
||||
|
@ -252,3 +252,11 @@ def test_complex():
|
||||
assert entry == HyComplex("1.0j")
|
||||
entry = tokenize("(j)")[0][0]
|
||||
assert entry == HySymbol("j")
|
||||
|
||||
|
||||
def test_reader_macro():
|
||||
"""Ensure reader macros are handles properly"""
|
||||
entry = tokenize("#^()")
|
||||
assert entry[0][0] == HySymbol("dispatch_reader_macro")
|
||||
assert entry[0][1] == HyExpression([HySymbol("quote"), HyString("^")])
|
||||
assert len(entry[0]) == 3
|
||||
|
11
tests/macros/test_reader_macros.py
Normal file
11
tests/macros/test_reader_macros.py
Normal file
@ -0,0 +1,11 @@
|
||||
from hy.macros import macroexpand
|
||||
from hy.compiler import HyTypeError
|
||||
from hy.lex import tokenize
|
||||
|
||||
|
||||
def test_reader_macro_error():
|
||||
"""Check if we get correct error with wrong disptach character"""
|
||||
try:
|
||||
macroexpand(tokenize("(dispatch_reader_macro '- '())")[0], __name__)
|
||||
except HyTypeError as e:
|
||||
assert "with the character `-`" in str(e)
|
32
tests/native_tests/reader_macros.hy
Normal file
32
tests/native_tests/reader_macros.hy
Normal file
@ -0,0 +1,32 @@
|
||||
(defn test-reader-macro []
|
||||
"Test a basic redaer macro"
|
||||
(defreader ^ [expr]
|
||||
expr)
|
||||
|
||||
(assert (= #^"works" "works")))
|
||||
|
||||
|
||||
(defn test-reader-macro-expr []
|
||||
"Test basic exprs like lists and arrays"
|
||||
(defreader n [expr]
|
||||
(get expr 1))
|
||||
|
||||
(assert (= #n[1 2] 2))
|
||||
(assert (= #n(1 2) 2)))
|
||||
|
||||
|
||||
(defn test-reader-macro-override []
|
||||
"Test if we can override function symbols"
|
||||
(defreader + [n]
|
||||
(+ n 1))
|
||||
|
||||
(assert (= #+2 3)))
|
||||
|
||||
|
||||
(defn test-reader-macro-compile-docstring []
|
||||
"Test if we can compile with a docstring"
|
||||
(try
|
||||
(defreader d []
|
||||
"Compiles with docstrings")
|
||||
(except [Exception]
|
||||
(assert False))))
|
Loading…
Reference in New Issue
Block a user