commit
e92ef484a0
6
NEWS
6
NEWS
@ -1,3 +1,9 @@
|
|||||||
|
Changes from 0.13.0
|
||||||
|
|
||||||
|
[ Language Changes ]
|
||||||
|
* Single-character "sharp macros" changed to "tag macros", which can have
|
||||||
|
longer names
|
||||||
|
|
||||||
Changes from 0.12.1
|
Changes from 0.12.1
|
||||||
|
|
||||||
[ Language Changes ]
|
[ Language Changes ]
|
||||||
|
@ -779,25 +779,21 @@ For example,
|
|||||||
42
|
42
|
||||||
|
|
||||||
|
|
||||||
defsharp
|
deftag
|
||||||
--------
|
--------
|
||||||
|
|
||||||
.. versionadded:: 0.13.0
|
.. versionadded:: 0.13.0
|
||||||
|
|
||||||
``defsharp`` defines a sharp macro. A sharp macro is a unary macro that has the
|
``deftag`` defines a tag macro. A tag macro is a unary macro that has the
|
||||||
same semantics as an ordinary macro defined with ``defmacro``, but can be
|
same semantics as an ordinary macro defined with ``defmacro``. It is called with
|
||||||
called without parentheses and with less whitespace. The name of a sharp macro
|
the syntax ``#tag FORM``, where ``tag`` is the name of the macro, and ``FORM``
|
||||||
must be exactly one character long. It is called with the syntax ``#cFORM``,
|
is any form. The ``tag`` is often only one character, but it can be any symbol.
|
||||||
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
|
.. code-block:: clj
|
||||||
|
|
||||||
=> (defsharp ♣ [expr] `[~expr ~expr])
|
=> (deftag ♣ [expr] `[~expr ~expr])
|
||||||
<function <lambda> at 0x7f76d0271158>
|
<function <lambda> at 0x7f76d0271158>
|
||||||
=> #♣5
|
=> #♣ 5
|
||||||
[5, 5]
|
[5, 5]
|
||||||
=> (setv x 0)
|
=> (setv x 0)
|
||||||
=> #♣(+= x 1)
|
=> #♣(+= x 1)
|
||||||
@ -805,13 +801,13 @@ required.
|
|||||||
=> x
|
=> x
|
||||||
2
|
2
|
||||||
|
|
||||||
In this example, if you used ``(defmacro ♣ ...)`` instead of ``(defsharp
|
In this example, if you used ``(defmacro ♣ ...)`` instead of ``(deftag
|
||||||
♣ ...)``, you would call the macro as ``(♣ 5)`` or ``(♣ (+= x 1))``.
|
♣ ...)``, you would call the macro as ``(♣ 5)`` or ``(♣ (+= x 1))``.
|
||||||
|
|
||||||
The syntax for calling sharp macros is similar to that of reader macros a la
|
The syntax for calling tag 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
|
Common Lisp's ``SET-MACRO-CHARACTER``. In fact, before Hy 0.13.0, tag macros
|
||||||
were called "reader macros", and defined with ``defreader`` rather than
|
were called "reader macros", and defined with ``defreader`` rather than
|
||||||
``defsharp``. True reader macros are not (yet) implemented in Hy.
|
``deftag``. True reader macros are not (yet) implemented in Hy.
|
||||||
|
|
||||||
del
|
del
|
||||||
---
|
---
|
||||||
@ -1731,7 +1727,7 @@ will be 4 (``1+1 + 1+1``).
|
|||||||
|
|
||||||
.. versionadded:: 0.12.0
|
.. versionadded:: 0.12.0
|
||||||
|
|
||||||
The sharp macro ``#@`` can be used as a shorthand for ``with-decorator``. With
|
The tag macro ``#@`` can be used as a shorthand for ``with-decorator``. With
|
||||||
``#@``, the previous example becomes:
|
``#@``, the previous example becomes:
|
||||||
|
|
||||||
.. code-block:: clj
|
.. code-block:: clj
|
||||||
|
@ -582,13 +582,13 @@ elements, so by the time program started executing, it actually reads:
|
|||||||
(+ 1 2 3)
|
(+ 1 2 3)
|
||||||
|
|
||||||
Sometimes it's nice to be able to call a one-parameter macro without
|
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
|
parentheses. Tag macros allow this. The name of a tag macro is typically
|
||||||
one character long, but since Hy operates well with Unicode, we aren't running
|
one character long, but since Hy operates well with Unicode, we aren't running
|
||||||
out of characters that soon:
|
out of characters that soon:
|
||||||
|
|
||||||
.. code-block:: clj
|
.. code-block:: clj
|
||||||
|
|
||||||
=> (defsharp ↻ [code]
|
=> (deftag ↻ [code]
|
||||||
... (setv op (last code) params (list (butlast code)))
|
... (setv op (last code) params (list (butlast code)))
|
||||||
... `(~op ~@params))
|
... `(~op ~@params))
|
||||||
=> #↻(1 2 3 +)
|
=> #↻(1 2 3 +)
|
||||||
|
33
hy/compiler.py
Normal file → Executable file
33
hy/compiler.py
Normal file → Executable file
@ -13,7 +13,7 @@ from hy.lex.parser import hy_symbol_mangle
|
|||||||
import hy.macros
|
import hy.macros
|
||||||
from hy._compat import (
|
from hy._compat import (
|
||||||
str_type, bytes_type, long_type, PY3, PY34, PY35, raise_empty)
|
str_type, bytes_type, long_type, PY3, PY34, PY35, raise_empty)
|
||||||
from hy.macros import require, macroexpand, sharp_macroexpand
|
from hy.macros import require, macroexpand, tag_macroexpand
|
||||||
import hy.importer
|
import hy.importer
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
@ -2465,20 +2465,20 @@ class HyASTCompiler(object):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@builds("defsharp")
|
@builds("deftag")
|
||||||
@checkargs(min=2)
|
@checkargs(min=2)
|
||||||
def compile_sharp_macro(self, expression):
|
def compile_tag_macro(self, expression):
|
||||||
expression.pop(0)
|
expression.pop(0)
|
||||||
name = expression.pop(0)
|
name = expression.pop(0)
|
||||||
if name == ":" or name == "&" or len(name) > 1:
|
if name == ":" or name == "&":
|
||||||
raise NameError("%s can't be used as a sharp macro name" % name)
|
raise NameError("%s can't be used as a tag macro name" % name)
|
||||||
if not isinstance(name, HySymbol) and not isinstance(name, HyString):
|
if not isinstance(name, HySymbol) and not isinstance(name, HyString):
|
||||||
raise HyTypeError(name,
|
raise HyTypeError(name,
|
||||||
("received a `%s' instead of a symbol "
|
("received a `%s' instead of a symbol "
|
||||||
"for sharp macro name" % type(name).__name__))
|
"for tag macro name" % type(name).__name__))
|
||||||
name = HyString(name).replace(name)
|
name = HyString(name).replace(name)
|
||||||
new_expression = HyExpression([
|
new_expression = HyExpression([
|
||||||
HyExpression([HySymbol("hy.macros.sharp"), name]),
|
HyExpression([HySymbol("hy.macros.tag"), name]),
|
||||||
HyExpression([HySymbol("fn")] + expression),
|
HyExpression([HySymbol("fn")] + expression),
|
||||||
]).replace(expression)
|
]).replace(expression)
|
||||||
|
|
||||||
@ -2486,18 +2486,19 @@ class HyASTCompiler(object):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@builds("dispatch_sharp_macro")
|
@builds("dispatch_tag_macro")
|
||||||
@checkargs(exact=2)
|
@checkargs(exact=2)
|
||||||
def compile_dispatch_sharp_macro(self, expression):
|
def compile_dispatch_tag_macro(self, expression):
|
||||||
expression.pop(0) # dispatch-sharp-macro
|
expression.pop(0) # dispatch-tag-macro
|
||||||
str_char = expression.pop(0)
|
tag = expression.pop(0)
|
||||||
if not type(str_char) == HyString:
|
if not type(tag) == HyString:
|
||||||
raise HyTypeError(
|
raise HyTypeError(
|
||||||
str_char,
|
tag,
|
||||||
"Trying to expand a sharp macro using `{0}' instead "
|
"Trying to expand a tag macro using `{0}' instead "
|
||||||
"of string".format(type(str_char).__name__),
|
"of string".format(type(tag).__name__),
|
||||||
)
|
)
|
||||||
expr = sharp_macroexpand(str_char, expression.pop(0), self)
|
tag = HyString(hy_symbol_mangle(str(tag))).replace(tag)
|
||||||
|
expr = tag_macroexpand(tag, expression.pop(0), self)
|
||||||
return self.compile(expr)
|
return self.compile(expr)
|
||||||
|
|
||||||
@builds("eval_and_compile")
|
@builds("eval_and_compile")
|
||||||
|
@ -40,11 +40,11 @@ class Completer(object):
|
|||||||
builtins.__dict__,
|
builtins.__dict__,
|
||||||
hy.macros._hy_macros[None],
|
hy.macros._hy_macros[None],
|
||||||
namespace]
|
namespace]
|
||||||
self.sharp_path = [hy.macros._hy_sharp[None]]
|
self.tag_path = [hy.macros._hy_tag[None]]
|
||||||
if '__name__' in namespace:
|
if '__name__' in namespace:
|
||||||
module_name = namespace['__name__']
|
module_name = namespace['__name__']
|
||||||
self.path.append(hy.macros._hy_macros[module_name])
|
self.path.append(hy.macros._hy_macros[module_name])
|
||||||
self.sharp_path.append(hy.macros._hy_sharp[module_name])
|
self.tag_path.append(hy.macros._hy_tag[module_name])
|
||||||
|
|
||||||
def attr_matches(self, text):
|
def attr_matches(self, text):
|
||||||
# Borrowed from IPython's completer
|
# Borrowed from IPython's completer
|
||||||
@ -81,10 +81,10 @@ class Completer(object):
|
|||||||
matches.append(k)
|
matches.append(k)
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
def sharp_matches(self, text):
|
def tag_matches(self, text):
|
||||||
text = text[1:]
|
text = text[1:]
|
||||||
matches = []
|
matches = []
|
||||||
for p in self.sharp_path:
|
for p in self.tag_path:
|
||||||
for k in p.keys():
|
for k in p.keys():
|
||||||
if isinstance(k, string_types):
|
if isinstance(k, string_types):
|
||||||
if k.startswith(text):
|
if k.startswith(text):
|
||||||
@ -93,7 +93,7 @@ class Completer(object):
|
|||||||
|
|
||||||
def complete(self, text, state):
|
def complete(self, text, state):
|
||||||
if text.startswith("#"):
|
if text.startswith("#"):
|
||||||
matches = self.sharp_matches(text)
|
matches = self.tag_matches(text)
|
||||||
elif "." in text:
|
elif "." in text:
|
||||||
matches = self.attr_matches(text)
|
matches = self.attr_matches(text)
|
||||||
else:
|
else:
|
||||||
|
@ -230,7 +230,7 @@
|
|||||||
(sys.exit ~retval))))
|
(sys.exit ~retval))))
|
||||||
|
|
||||||
|
|
||||||
(defsharp @ [expr]
|
(deftag @ [expr]
|
||||||
(setv decorators (cut expr None -1)
|
(setv decorators (cut expr None -1)
|
||||||
fndef (get expr -1))
|
fndef (get expr -1))
|
||||||
`(with-decorator ~@decorators ~fndef))
|
`(with-decorator ~@decorators ~fndef))
|
||||||
|
5
hy/lex/lexer.py
Normal file → Executable file
5
hy/lex/lexer.py
Normal file → Executable file
@ -12,6 +12,7 @@ lg = LexerGenerator()
|
|||||||
# i.e. a space or a closing brace/paren/curly
|
# i.e. a space or a closing brace/paren/curly
|
||||||
end_quote = r'(?![\s\)\]\}])'
|
end_quote = r'(?![\s\)\]\}])'
|
||||||
|
|
||||||
|
identifier = r'[^()\[\]{}\'"\s;]+'
|
||||||
|
|
||||||
lg.add('LPAREN', r'\(')
|
lg.add('LPAREN', r'\(')
|
||||||
lg.add('RPAREN', r'\)')
|
lg.add('RPAREN', r'\)')
|
||||||
@ -25,7 +26,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('HASHOTHER', r'#[^{]')
|
lg.add('HASHOTHER', r'#%s' % identifier)
|
||||||
|
|
||||||
# A regexp which matches incomplete strings, used to support
|
# A regexp which matches incomplete strings, used to support
|
||||||
# multi-line strings in the interpreter
|
# multi-line strings in the interpreter
|
||||||
@ -44,7 +45,7 @@ partial_string = r'''(?x)
|
|||||||
lg.add('STRING', r'%s"' % partial_string)
|
lg.add('STRING', r'%s"' % partial_string)
|
||||||
lg.add('PARTIAL_STRING', partial_string)
|
lg.add('PARTIAL_STRING', partial_string)
|
||||||
|
|
||||||
lg.add('IDENTIFIER', r'[^()\[\]{}\'"\s;]+')
|
lg.add('IDENTIFIER', identifier)
|
||||||
|
|
||||||
|
|
||||||
lg.ignore(r';.*(?=\r|\n|$)')
|
lg.ignore(r';.*(?=\r|\n|$)')
|
||||||
|
5
hy/lex/parser.py
Normal file → Executable file
5
hy/lex/parser.py
Normal file → Executable file
@ -200,10 +200,11 @@ def term_unquote_splice(p):
|
|||||||
@pg.production("term : HASHOTHER term")
|
@pg.production("term : HASHOTHER term")
|
||||||
@set_quote_boundaries
|
@set_quote_boundaries
|
||||||
def hash_other(p):
|
def hash_other(p):
|
||||||
st = p[0].getstr()[1]
|
# p == [(Token('HASHOTHER', '#foo'), bar)]
|
||||||
|
st = p[0].getstr()[1:]
|
||||||
str_object = HyString(st)
|
str_object = HyString(st)
|
||||||
expr = p[1]
|
expr = p[1]
|
||||||
return HyExpression([HySymbol("dispatch_sharp_macro"), str_object, expr])
|
return HyExpression([HySymbol("dispatch_tag_macro"), str_object, expr])
|
||||||
|
|
||||||
|
|
||||||
@pg.production("set : HLCURLY list_contents RCURLY")
|
@pg.production("set : HLCURLY list_contents RCURLY")
|
||||||
|
28
hy/macros.py
28
hy/macros.py
@ -18,7 +18,7 @@ EXTRA_MACROS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
_hy_macros = defaultdict(dict)
|
_hy_macros = defaultdict(dict)
|
||||||
_hy_sharp = defaultdict(dict)
|
_hy_tag = defaultdict(dict)
|
||||||
|
|
||||||
|
|
||||||
def macro(name):
|
def macro(name):
|
||||||
@ -50,8 +50,8 @@ def macro(name):
|
|||||||
return _
|
return _
|
||||||
|
|
||||||
|
|
||||||
def sharp(name):
|
def tag(name):
|
||||||
"""Decorator to define a sharp macro called `name`.
|
"""Decorator to define a tag macro called `name`.
|
||||||
|
|
||||||
This stores the macro `name` in the namespace for the module where it is
|
This stores the macro `name` in the namespace for the module where it is
|
||||||
defined.
|
defined.
|
||||||
@ -59,14 +59,14 @@ def sharp(name):
|
|||||||
If the module where it is defined is in `hy.core`, then the macro is stored
|
If the module where it is defined is in `hy.core`, then the macro is stored
|
||||||
in the default `None` namespace.
|
in the default `None` namespace.
|
||||||
|
|
||||||
This function is called from the `defsharp` special form in the compiler.
|
This function is called from the `deftag` special form in the compiler.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def _(fn):
|
def _(fn):
|
||||||
module_name = fn.__module__
|
module_name = fn.__module__
|
||||||
if module_name.startswith("hy.core"):
|
if module_name.startswith("hy.core"):
|
||||||
module_name = None
|
module_name = None
|
||||||
_hy_sharp[module_name][name] = fn
|
_hy_tag[module_name][name] = fn
|
||||||
|
|
||||||
return fn
|
return fn
|
||||||
return _
|
return _
|
||||||
@ -90,7 +90,7 @@ def require(source_module, target_module,
|
|||||||
if prefix:
|
if prefix:
|
||||||
prefix += "."
|
prefix += "."
|
||||||
|
|
||||||
for d in _hy_macros, _hy_sharp:
|
for d in _hy_macros, _hy_tag:
|
||||||
for name, macro in d[source_module].items():
|
for name, macro in d[source_module].items():
|
||||||
seen_names.add(name)
|
seen_names.add(name)
|
||||||
if all_macros:
|
if all_macros:
|
||||||
@ -210,19 +210,19 @@ def macroexpand_1(tree, compiler):
|
|||||||
return tree
|
return tree
|
||||||
|
|
||||||
|
|
||||||
def sharp_macroexpand(char, tree, compiler):
|
def tag_macroexpand(tag, tree, compiler):
|
||||||
"""Expand the sharp macro "char" with argument `tree`."""
|
"""Expand the tag macro "tag" with argument `tree`."""
|
||||||
load_macros(compiler.module_name)
|
load_macros(compiler.module_name)
|
||||||
|
|
||||||
sharp_macro = _hy_sharp[compiler.module_name].get(char)
|
tag_macro = _hy_tag[compiler.module_name].get(tag)
|
||||||
if sharp_macro is None:
|
if tag_macro is None:
|
||||||
try:
|
try:
|
||||||
sharp_macro = _hy_sharp[None][char]
|
tag_macro = _hy_tag[None][tag]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise HyTypeError(
|
raise HyTypeError(
|
||||||
char,
|
tag,
|
||||||
"`{0}' is not a defined sharp macro.".format(char)
|
"`{0}' is not a defined tag macro.".format(tag)
|
||||||
)
|
)
|
||||||
|
|
||||||
expr = sharp_macro(tree)
|
expr = tag_macro(tree)
|
||||||
return replace_hy_obj(wrap_value(expr), tree)
|
return replace_hy_obj(wrap_value(expr), tree)
|
||||||
|
@ -7,10 +7,10 @@ from hy.compiler import HyTypeError, HyASTCompiler
|
|||||||
from hy.lex import tokenize
|
from hy.lex import tokenize
|
||||||
|
|
||||||
|
|
||||||
def test_sharp_macro_error():
|
def test_tag_macro_error():
|
||||||
"""Check if we get correct error with wrong dispatch character"""
|
"""Check if we get correct error with wrong dispatch character"""
|
||||||
try:
|
try:
|
||||||
macroexpand(tokenize("(dispatch_sharp_macro '- '())")[0],
|
macroexpand(tokenize("(dispatch_tag_macro '- '())")[0],
|
||||||
HyASTCompiler(__name__))
|
HyASTCompiler(__name__))
|
||||||
except HyTypeError as e:
|
except HyTypeError as e:
|
||||||
assert "with the character `-`" in str(e)
|
assert "with the character `-`" in str(e)
|
@ -1,83 +0,0 @@
|
|||||||
;; Copyright 2017 the authors.
|
|
||||||
;; This file is part of Hy, which is free software licensed under the Expat
|
|
||||||
;; license. See the LICENSE.
|
|
||||||
|
|
||||||
(import [functools [wraps]])
|
|
||||||
|
|
||||||
|
|
||||||
(defn test-sharp-macro []
|
|
||||||
"Test a basic sharp macro"
|
|
||||||
(defsharp ^ [expr]
|
|
||||||
expr)
|
|
||||||
|
|
||||||
(assert (= #^"works" "works")))
|
|
||||||
|
|
||||||
|
|
||||||
(defn test-sharp-macro-expr []
|
|
||||||
"Test basic exprs like lists and arrays"
|
|
||||||
(defsharp n [expr]
|
|
||||||
(get expr 1))
|
|
||||||
|
|
||||||
(assert (= #n[1 2] 2))
|
|
||||||
(assert (= #n(1 2) 2)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn test-sharp-macro-override []
|
|
||||||
"Test if we can override function symbols"
|
|
||||||
(defsharp + [n]
|
|
||||||
(+ n 1))
|
|
||||||
|
|
||||||
(assert (= #+2 3)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn test-sharp-macros-macros []
|
|
||||||
"Test if defsharp is actually a macro"
|
|
||||||
(defsharp t [expr]
|
|
||||||
`(, ~@expr))
|
|
||||||
|
|
||||||
(def a #t[1 2 3])
|
|
||||||
|
|
||||||
(assert (= (type a) tuple))
|
|
||||||
(assert (= (, 1 2 3) a)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn test-sharp-macro-string-name []
|
|
||||||
"Test if defsharp accepts a string as a macro name."
|
|
||||||
|
|
||||||
(defsharp "." [expr]
|
|
||||||
expr)
|
|
||||||
|
|
||||||
(assert (= #."works" "works")))
|
|
||||||
|
|
||||||
|
|
||||||
(defn test-builtin-decorator-sharp []
|
|
||||||
(defn increment-arguments [func]
|
|
||||||
"Increments each argument passed to the decorated function."
|
|
||||||
((wraps func)
|
|
||||||
(fn [&rest args &kwargs kwargs]
|
|
||||||
(apply func
|
|
||||||
(map inc args)
|
|
||||||
(dict-comp k (inc v) [[k v] (.items kwargs)])))))
|
|
||||||
|
|
||||||
#@(increment-arguments
|
|
||||||
(defn foo [&rest args &kwargs kwargs]
|
|
||||||
"Bar."
|
|
||||||
(, args kwargs)))
|
|
||||||
|
|
||||||
;; The decorator did what it was supposed to
|
|
||||||
(assert (= (, (, 2 3 4) {"quux" 5 "baz" 6})
|
|
||||||
(foo 1 2 3 :quux 4 :baz 5)))
|
|
||||||
|
|
||||||
;; @wraps preserved the docstring and __name__
|
|
||||||
(assert (= "foo" (. foo --name--)))
|
|
||||||
(assert (= "Bar." (. foo --doc--)))
|
|
||||||
|
|
||||||
;; We can use the #@ sharp macro to apply more than one decorator
|
|
||||||
#@(increment-arguments
|
|
||||||
increment-arguments
|
|
||||||
(defn double-foo [&rest args &kwargs kwargs]
|
|
||||||
"Bar."
|
|
||||||
(, args kwargs)))
|
|
||||||
|
|
||||||
(assert (= (, (, 3 4 5) {"quux" 6 "baz" 7})
|
|
||||||
(double-foo 1 2 3 :quux 4 :baz 5))))
|
|
128
tests/native_tests/tag_macros.hy
Normal file
128
tests/native_tests/tag_macros.hy
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
;; Copyright 2017 the authors.
|
||||||
|
;; This file is part of Hy, which is free software licensed under the Expat
|
||||||
|
;; license. See the LICENSE.
|
||||||
|
|
||||||
|
(import [functools [wraps]])
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-tag-macro []
|
||||||
|
"Test a basic tag macro"
|
||||||
|
(deftag ^ [expr]
|
||||||
|
expr)
|
||||||
|
|
||||||
|
(assert (= #^"works" "works")))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-long-tag-macro []
|
||||||
|
"Test a tag macro with a name longer than one character"
|
||||||
|
(deftag foo [expr]
|
||||||
|
`['foo ~expr])
|
||||||
|
(assert (= #foo'bar ['foo 'bar]))
|
||||||
|
(assert (= #foo"baz" ['foo "baz"]))
|
||||||
|
(assert (= #foo(- 44 2) ['foo 42]))
|
||||||
|
(assert (= #foo(, 42) ['foo (, 42)]))
|
||||||
|
(assert (= #foo[42] ['foo [42]]))
|
||||||
|
(assert (= #foo{4 2} ['foo {4 2}])))
|
||||||
|
|
||||||
|
(defn test-hyphenated-tag-macro []
|
||||||
|
"Test if hyphens translate properly"
|
||||||
|
(deftag foo-bar [x]
|
||||||
|
`['foo ~x 'bar])
|
||||||
|
(assert (= #foo-bar 42) ['foo 42 'bar])
|
||||||
|
(assert (= #foo_bar 42) ['foo 42 'bar])
|
||||||
|
(deftag spam_eggs [x]
|
||||||
|
`['spam ~x 'eggs])
|
||||||
|
(assert (= #spam-eggs 42 ['spam 42 'eggs]))
|
||||||
|
(assert (= #spam_eggs 42 ['spam 42 'eggs])))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-tag-macro-whitespace []
|
||||||
|
"Test whitespace after a tag macro"
|
||||||
|
(deftag foo [expr]
|
||||||
|
`['foo ~expr])
|
||||||
|
(assert (= #foo 42) ['foo 42])
|
||||||
|
(assert (= #foo (- 44 2) ['foo 42]))
|
||||||
|
(deftag b [x]
|
||||||
|
`['bar ~x])
|
||||||
|
(assert (= #b 42) ['bar 42])
|
||||||
|
; # is allowed in tags, so this must be separated
|
||||||
|
(assert (= #b #{42} ['bar #{42}]))
|
||||||
|
; multiple tags must likewise be separated
|
||||||
|
(assert (= #b #foo 42 ['bar ['foo 42]]))
|
||||||
|
; newlines are also whitespace
|
||||||
|
(assert (= #foo
|
||||||
|
|
||||||
|
42 ['foo 42]))
|
||||||
|
(assert (= #foo; a semicolon/comment should count as whitespace
|
||||||
|
42
|
||||||
|
['foo 42])))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-tag-macro-expr []
|
||||||
|
"Test basic exprs like lists and arrays"
|
||||||
|
(deftag n [expr]
|
||||||
|
(get expr 1))
|
||||||
|
|
||||||
|
(assert (= #n[1 2] 2))
|
||||||
|
(assert (= #n(1 2) 2)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-tag-macro-override []
|
||||||
|
"Test if we can override function symbols"
|
||||||
|
(deftag + [n]
|
||||||
|
(+ n 1))
|
||||||
|
|
||||||
|
(assert (= #+2 3)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-tag-macros-macros []
|
||||||
|
"Test if deftag is actually a macro"
|
||||||
|
(deftag t [expr]
|
||||||
|
`(, ~@expr))
|
||||||
|
|
||||||
|
(def a #t[1 2 3])
|
||||||
|
|
||||||
|
(assert (= (type a) tuple))
|
||||||
|
(assert (= (, 1 2 3) a)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-tag-macro-string-name []
|
||||||
|
"Test if deftag accepts a string as a macro name."
|
||||||
|
|
||||||
|
(deftag "." [expr]
|
||||||
|
expr)
|
||||||
|
|
||||||
|
(assert (= #."works" "works")))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-builtin-decorator-tag []
|
||||||
|
(defn increment-arguments [func]
|
||||||
|
"Increments each argument passed to the decorated function."
|
||||||
|
((wraps func)
|
||||||
|
(fn [&rest args &kwargs kwargs]
|
||||||
|
(apply func
|
||||||
|
(map inc args)
|
||||||
|
(dict-comp k (inc v) [[k v] (.items kwargs)])))))
|
||||||
|
|
||||||
|
#@(increment-arguments
|
||||||
|
(defn foo [&rest args &kwargs kwargs]
|
||||||
|
"Bar."
|
||||||
|
(, args kwargs)))
|
||||||
|
|
||||||
|
;; The decorator did what it was supposed to
|
||||||
|
(assert (= (, (, 2 3 4) {"quux" 5 "baz" 6})
|
||||||
|
(foo 1 2 3 :quux 4 :baz 5)))
|
||||||
|
|
||||||
|
;; @wraps preserved the docstring and __name__
|
||||||
|
(assert (= "foo" (. foo --name--)))
|
||||||
|
(assert (= "Bar." (. foo --doc--)))
|
||||||
|
|
||||||
|
;; We can use the #@ tag macro to apply more than one decorator
|
||||||
|
#@(increment-arguments
|
||||||
|
increment-arguments
|
||||||
|
(defn double-foo [&rest args &kwargs kwargs]
|
||||||
|
"Bar."
|
||||||
|
(, args kwargs)))
|
||||||
|
|
||||||
|
(assert (= (, (, 3 4 5) {"quux" 6 "baz" 7})
|
||||||
|
(double-foo 1 2 3 :quux 4 :baz 5))))
|
@ -303,10 +303,10 @@ def test_complex():
|
|||||||
assert entry == HySymbol("j")
|
assert entry == HySymbol("j")
|
||||||
|
|
||||||
|
|
||||||
def test_sharp_macro():
|
def test_tag_macro():
|
||||||
"""Ensure sharp macros are handled properly"""
|
"""Ensure tag macros are handled properly"""
|
||||||
entry = tokenize("#^()")
|
entry = tokenize("#^()")
|
||||||
assert entry[0][0] == HySymbol("dispatch_sharp_macro")
|
assert entry[0][0] == HySymbol("dispatch_tag_macro")
|
||||||
assert entry[0][1] == HyString("^")
|
assert entry[0][1] == HyString("^")
|
||||||
assert len(entry[0]) == 3
|
assert len(entry[0]) == 3
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user