diff --git a/docs/language/readermacros.rst b/docs/language/readermacros.rst index 0c05d9c..a87b98d 100644 --- a/docs/language/readermacros.rst +++ b/docs/language/readermacros.rst @@ -24,19 +24,28 @@ Syntax => #^1+2+3+4+3+2 1+2+3+4+3+2 +Hy got 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 like clojure, and have a litteral for regular expressions! + +:: + + => (import re) + => (defreader r [expr] `(re.compile ~expr)) + => #r".*" + <_sre.SRE_Pattern object at 0xcv7713ph15#> + Implementation ============== -Hy uses ``defreader`` to define the reader symbol, and ``#`` as the dispatch -character. ``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol -and expression is quoted, and then passed along to the correct function:: - - => (defreader ^ ...) - => #^() - ;=> (dispatch_reader_macro '^ '()) - - ``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 decorater saves the @@ -47,14 +56,17 @@ lambda in a dict with its module name and symbol. => (defreader ^ [expr] (print expr)) ;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr))) - -Anything passed along is quoted, thus given to the function defined. +``#`` 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 diff --git a/hy/compiler.py b/hy/compiler.py index e9be303..93e6526 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -38,8 +38,8 @@ from hy.models.dict import HyDict from hy.errors import HyCompileError, HyTypeError import hy.macros -from hy.macros import require, macroexpand from hy._compat import str_type, long_type, PY27, PY33, PY3, PY34 +from hy.macros import require, macroexpand, reader_macroexpand import hy.importer import traceback @@ -1980,7 +1980,7 @@ class HyASTCompiler(object): return ret @builds("defreader") - @checkargs(min=2, max=3) + @checkargs(min=2) def compile_reader(self, expression): expression.pop(0) name = expression.pop(0) @@ -2002,6 +2002,23 @@ class HyASTCompiler(object): return ret + @builds("dispatch_reader_macro") + @checkargs(exact=2) + def compile_dispatch_reader_macro(self, expression): + expression.pop(0) # dispatch-reader-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 " + "of string".format(type(str_char).__name__), + ) + + module = self.module_name + expr = reader_macroexpand(str_char, expression.pop(0), module) + + return self.compile(expr) + @builds("eval_and_compile") def compile_eval_and_compile(self, expression): expression[0] = HySymbol("progn") diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 79d7c12..9219975 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -181,13 +181,3 @@ (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)))) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 72f0d8f..aac626a 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -154,8 +154,8 @@ def term_unquote_splice(p): @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]]) + str_object = HyString(st) + expr = p[1] return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr]) diff --git a/hy/macros.py b/hy/macros.py index d98b328..b155799 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -43,7 +43,6 @@ EXTRA_MACROS = [ _hy_macros = defaultdict(dict) _hy_reader = defaultdict(dict) -_hy_reader_chars = set() def macro(name): @@ -85,8 +84,6 @@ def reader(name): module_name = None _hy_reader[module_name][name] = fn - # Ugly hack to get some error handling - _hy_reader_chars.add(name) return fn return _ @@ -209,3 +206,20 @@ def macroexpand_1(tree, module_name): return ntree return tree + + +def reader_macroexpand(char, tree, module_name): + """Expand the reader macro "char" with argument `tree`.""" + load_macros(module_name) + + if not char in _hy_reader[module_name]: + raise HyTypeError( + char, + "`{0}' is not a reader macro in module '{1}'".format( + char, + module_name, + ), + ) + + expr = _hy_reader[module_name][char](tree) + return _wrap_value(expr).replace(tree) diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index af9f286..eb95e80 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -258,7 +258,7 @@ 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 entry[0][1] == HyString("^") assert len(entry[0]) == 3 diff --git a/tests/native_tests/reader_macros.hy b/tests/native_tests/reader_macros.hy index e43220b..84a48a5 100644 --- a/tests/native_tests/reader_macros.hy +++ b/tests/native_tests/reader_macros.hy @@ -23,10 +23,14 @@ (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)))) +(defn test-reader-macros-macros [] + "Test if defreader is actually a macro" + (defreader t [expr] + `(, ~@expr)) + + (def a #t[1 2 3]) + + (assert (= (type a) tuple)) + (assert (= (, 1 2 3) a))) + +