Fix reader macros to actually be macros

This commit is contained in:
Foxboron 2014-01-14 02:38:16 +01:00
parent d34c22eeac
commit a35ecc41bd
7 changed files with 73 additions and 36 deletions

View File

@ -24,19 +24,28 @@ Syntax
=> #^1+2+3+4+3+2 => #^1+2+3+4+3+2
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 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, ``defreader`` takes a single character as symbol name for the reader macro,
anything longer will return an error. Implementation wise, ``defreader`` anything longer will return an error. Implementation wise, ``defreader``
expands into a lambda covered with a decorator, this decorater saves the 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)) => (defreader ^ [expr] (print expr))
;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr))) ;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr)))
``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol
Anything passed along is quoted, thus given to the function defined. and expression is passed to the correct function.
:: ::
=> #^()
;=> (dispatch_reader_macro ^ ())
=> #^"Hello" => #^"Hello"
"Hello" "Hello"
.. warning:: .. warning::
Because of a limitation in Hy's lexer and parser, reader macros can't Because of a limitation in Hy's lexer and parser, reader macros can't
redefine defined syntax such as ``()[]{}``. This will most likely be redefine defined syntax such as ``()[]{}``. This will most likely be

View File

@ -38,8 +38,8 @@ from hy.models.dict import HyDict
from hy.errors import HyCompileError, HyTypeError from hy.errors import HyCompileError, HyTypeError
import hy.macros import hy.macros
from hy.macros import require, macroexpand
from hy._compat import str_type, long_type, PY27, PY33, PY3, PY34 from hy._compat import str_type, long_type, PY27, PY33, PY3, PY34
from hy.macros import require, macroexpand, reader_macroexpand
import hy.importer import hy.importer
import traceback import traceback
@ -1980,7 +1980,7 @@ class HyASTCompiler(object):
return ret return ret
@builds("defreader") @builds("defreader")
@checkargs(min=2, max=3) @checkargs(min=2)
def compile_reader(self, expression): def compile_reader(self, expression):
expression.pop(0) expression.pop(0)
name = expression.pop(0) name = expression.pop(0)
@ -2002,6 +2002,23 @@ class HyASTCompiler(object):
return ret 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") @builds("eval_and_compile")
def compile_eval_and_compile(self, expression): def compile_eval_and_compile(self, expression):
expression[0] = HySymbol("progn") expression[0] = HySymbol("progn")

View File

@ -181,13 +181,3 @@
(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))))

View File

@ -154,8 +154,8 @@ def term_unquote_splice(p):
@set_quote_boundaries @set_quote_boundaries
def hash_reader(p): def hash_reader(p):
st = p[0].getstr()[1] st = p[0].getstr()[1]
str_object = HyExpression([HySymbol("quote"), HyString(st)]) str_object = HyString(st)
expr = HyExpression([HySymbol("quote"), p[1]]) expr = p[1]
return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr]) return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr])

View File

@ -43,7 +43,6 @@ EXTRA_MACROS = [
_hy_macros = defaultdict(dict) _hy_macros = defaultdict(dict)
_hy_reader = defaultdict(dict) _hy_reader = defaultdict(dict)
_hy_reader_chars = set()
def macro(name): def macro(name):
@ -85,8 +84,6 @@ def reader(name):
module_name = None module_name = None
_hy_reader[module_name][name] = fn _hy_reader[module_name][name] = fn
# Ugly hack to get some error handling
_hy_reader_chars.add(name)
return fn return fn
return _ return _
@ -209,3 +206,20 @@ def macroexpand_1(tree, module_name):
return ntree return ntree
return tree 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)

View File

@ -258,7 +258,7 @@ def test_reader_macro():
"""Ensure reader macros are handles properly""" """Ensure reader macros are handles properly"""
entry = tokenize("#^()") entry = tokenize("#^()")
assert entry[0][0] == HySymbol("dispatch_reader_macro") 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 assert len(entry[0]) == 3

View File

@ -23,10 +23,14 @@
(assert (= #+2 3))) (assert (= #+2 3)))
(defn test-reader-macro-compile-docstring [] (defn test-reader-macros-macros []
"Test if we can compile with a docstring" "Test if defreader is actually a macro"
(try (defreader t [expr]
(defreader d [] `(, ~@expr))
"Compiles with docstrings")
(except [Exception] (def a #t[1 2 3])
(assert False))))
(assert (= (type a) tuple))
(assert (= (, 1 2 3) a)))