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.list import HyList
|
||||||
from hy.models.dict import HyDict
|
from hy.models.dict import HyDict
|
||||||
|
|
||||||
|
import hy.macros
|
||||||
from hy.macros import require, macroexpand
|
from hy.macros import require, macroexpand
|
||||||
from hy._compat import str_type, long_type
|
from hy._compat import str_type, long_type
|
||||||
import hy.importer
|
import hy.importer
|
||||||
@ -1753,6 +1754,19 @@ class HyASTCompiler(object):
|
|||||||
bases=bases_expr,
|
bases=bases_expr,
|
||||||
body=body.stmts)
|
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")
|
@builds("defmacro")
|
||||||
@checkargs(min=1)
|
@checkargs(min=1)
|
||||||
def compile_macro(self, expression):
|
def compile_macro(self, expression):
|
||||||
@ -1768,16 +1782,30 @@ class HyASTCompiler(object):
|
|||||||
HyExpression([HySymbol("fn")] + expression),
|
HyExpression([HySymbol("fn")] + expression),
|
||||||
]).replace(expression)
|
]).replace(expression)
|
||||||
|
|
||||||
# Compile-time hack: we want to get our new macro now
|
ret = self._compile_time_hack(new_expression)
|
||||||
# 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)
|
|
||||||
|
|
||||||
# We really want to have a `hy` import to get hy.macro in
|
return ret
|
||||||
ret = self.compile(new_expression)
|
|
||||||
ret.add_imports('hy', [None])
|
@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
|
return ret
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
;;; These macros form the hy language
|
;;; These macros form the hy language
|
||||||
;;; They are automatically required in every module, except inside hy.core
|
;;; They are automatically required in every module, except inside hy.core
|
||||||
|
|
||||||
|
|
||||||
(defmacro for [args &rest body]
|
(defmacro for [args &rest body]
|
||||||
"shorthand for nested foreach loops:
|
"shorthand for nested foreach loops:
|
||||||
(for [x foo y bar] baz) ->
|
(for [x foo y bar] baz) ->
|
||||||
@ -144,3 +143,13 @@
|
|||||||
(setv -args (cdr (car -args))))
|
(setv -args (cdr (car -args))))
|
||||||
|
|
||||||
`(apply ~-fun [~@-args] (dict (sum ~-okwargs [])))))
|
`(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('UNQUOTESPLICE', r'~@%s' % end_quote)
|
||||||
lg.add('UNQUOTE', r'~%s' % end_quote)
|
lg.add('UNQUOTE', r'~%s' % end_quote)
|
||||||
lg.add('HASHBANG', r'#!.*[^\r\n]')
|
lg.add('HASHBANG', r'#!.*[^\r\n]')
|
||||||
|
lg.add('HASHREADER', r'#.')
|
||||||
|
|
||||||
|
|
||||||
lg.add('STRING', r'''(?x)
|
lg.add('STRING', r'''(?x)
|
||||||
|
@ -150,6 +150,15 @@ def term_unquote_splice(p):
|
|||||||
return HyExpression([HySymbol("unquote_splice"), p[1]])
|
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")
|
@pg.production("dict : LCURLY list_contents RCURLY")
|
||||||
@set_boundaries
|
@set_boundaries
|
||||||
def t_dict(p):
|
def t_dict(p):
|
||||||
|
31
hy/macros.py
31
hy/macros.py
@ -40,6 +40,8 @@ EXTRA_MACROS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
_hy_macros = defaultdict(dict)
|
_hy_macros = defaultdict(dict)
|
||||||
|
_hy_reader = defaultdict(dict)
|
||||||
|
_hy_reader_chars = set()
|
||||||
|
|
||||||
|
|
||||||
def macro(name):
|
def macro(name):
|
||||||
@ -63,6 +65,30 @@ def macro(name):
|
|||||||
return _
|
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):
|
def require(source_module, target_module):
|
||||||
"""Load the macros from `source_module` in the namespace of
|
"""Load the macros from `source_module` in the namespace of
|
||||||
`target_module`.
|
`target_module`.
|
||||||
@ -75,6 +101,11 @@ def require(source_module, target_module):
|
|||||||
for name, macro in macros.items():
|
for name, macro in macros.items():
|
||||||
refs[name] = macro
|
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
|
# type -> wrapping function mapping for _wrap_value
|
||||||
_wrappers = {
|
_wrappers = {
|
||||||
|
@ -11,3 +11,4 @@ from .native_tests.unless import * # noqa
|
|||||||
from .native_tests.when import * # noqa
|
from .native_tests.when import * # noqa
|
||||||
from .native_tests.with_decorator import * # noqa
|
from .native_tests.with_decorator import * # noqa
|
||||||
from .native_tests.core 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")
|
assert entry == HyComplex("1.0j")
|
||||||
entry = tokenize("(j)")[0][0]
|
entry = tokenize("(j)")[0][0]
|
||||||
assert entry == HySymbol("j")
|
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