From 20c26a52e4779bdfb3b30d7d871fc3aefc932efa Mon Sep 17 00:00:00 2001 From: gilch Date: Tue, 9 May 2017 19:54:32 -0600 Subject: [PATCH 1/4] make sharp macros take arbitrary identifiers Previously, only a single character was allowed. --- docs/language/api.rst | 12 ++++-------- docs/tutorial.rst | 2 +- hy/compiler.py | 13 +++++++------ hy/lex/lexer.py | 5 +++-- hy/lex/parser.py | 3 ++- hy/macros.py | 12 ++++++------ 6 files changed, 23 insertions(+), 24 deletions(-) mode change 100644 => 100755 hy/compiler.py mode change 100644 => 100755 hy/lex/lexer.py mode change 100644 => 100755 hy/lex/parser.py diff --git a/docs/language/api.rst b/docs/language/api.rst index a31b6c8..f36cbd5 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -785,19 +785,15 @@ defsharp .. versionadded:: 0.13.0 ``defsharp`` defines a sharp macro. A sharp macro is a unary macro that has the -same semantics as an ordinary macro defined with ``defmacro``, but can be -called without parentheses and with less whitespace. The name of a sharp macro -must be exactly one character long. It is called with the syntax ``#cFORM``, -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. +same semantics as an ordinary macro defined with ``defmacro``. It is called with +the syntax ``#tag FORM``, where ``tag`` is the name of the macro, and ``FORM`` +is any form. The ``tag`` is often only one character, but it can be any symbol. .. code-block:: clj => (defsharp ♣ [expr] `[~expr ~expr]) at 0x7f76d0271158> - => #♣5 + => #♣ 5 [5, 5] => (setv x 0) => #♣(+= x 1) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f0231cd..030a136 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -582,7 +582,7 @@ elements, so by the time program started executing, it actually reads: (+ 1 2 3) 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. Sharp macros allow this. The name of a sharp macro is typically one character long, but since Hy operates well with Unicode, we aren't running out of characters that soon: diff --git a/hy/compiler.py b/hy/compiler.py old mode 100644 new mode 100755 index 157cdcb..d9c11b1 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -2470,7 +2470,7 @@ class HyASTCompiler(object): def compile_sharp_macro(self, expression): 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) if not isinstance(name, HySymbol) and not isinstance(name, HyString): raise HyTypeError(name, @@ -2490,14 +2490,15 @@ class HyASTCompiler(object): @checkargs(exact=2) def compile_dispatch_sharp_macro(self, expression): expression.pop(0) # dispatch-sharp-macro - str_char = expression.pop(0) - if not type(str_char) == HyString: + tag = expression.pop(0) + if not type(tag) == HyString: raise HyTypeError( - str_char, + tag, "Trying to expand a sharp 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 = sharp_macroexpand(tag, expression.pop(0), self) return self.compile(expr) @builds("eval_and_compile") diff --git a/hy/lex/lexer.py b/hy/lex/lexer.py old mode 100644 new mode 100755 index 4039543..c6b5636 --- a/hy/lex/lexer.py +++ b/hy/lex/lexer.py @@ -12,6 +12,7 @@ lg = LexerGenerator() # i.e. a space or a closing brace/paren/curly end_quote = r'(?![\s\)\]\}])' +identifier = r'[^()\[\]{}\'"\s;]+' lg.add('LPAREN', 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('UNQUOTE', r'~%s' % end_quote) 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 # multi-line strings in the interpreter @@ -44,7 +45,7 @@ partial_string = r'''(?x) lg.add('STRING', r'%s"' % partial_string) lg.add('PARTIAL_STRING', partial_string) -lg.add('IDENTIFIER', r'[^()\[\]{}\'"\s;]+') +lg.add('IDENTIFIER', identifier) lg.ignore(r';.*(?=\r|\n|$)') diff --git a/hy/lex/parser.py b/hy/lex/parser.py old mode 100644 new mode 100755 index 6e1aa09..e43d188 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -200,7 +200,8 @@ def term_unquote_splice(p): @pg.production("term : HASHOTHER term") @set_quote_boundaries def hash_other(p): - st = p[0].getstr()[1] + # p == [(Token('HASHOTHER', '#foo'), bar)] + st = p[0].getstr()[1:] str_object = HyString(st) expr = p[1] return HyExpression([HySymbol("dispatch_sharp_macro"), str_object, expr]) diff --git a/hy/macros.py b/hy/macros.py index 84b192f..3da2bdb 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -210,18 +210,18 @@ def macroexpand_1(tree, compiler): return tree -def sharp_macroexpand(char, tree, compiler): - """Expand the sharp macro "char" with argument `tree`.""" +def sharp_macroexpand(tag, tree, compiler): + """Expand the sharp macro "tag" with argument `tree`.""" load_macros(compiler.module_name) - sharp_macro = _hy_sharp[compiler.module_name].get(char) + sharp_macro = _hy_sharp[compiler.module_name].get(tag) if sharp_macro is None: try: - sharp_macro = _hy_sharp[None][char] + sharp_macro = _hy_sharp[None][tag] except KeyError: raise HyTypeError( - char, - "`{0}' is not a defined sharp macro.".format(char) + tag, + "`{0}' is not a defined sharp macro.".format(tag) ) expr = sharp_macro(tree) From 6cd3201421da39041d247c2f55963736ee7ae830 Mon Sep 17 00:00:00 2001 From: gilch Date: Tue, 20 Jun 2017 21:48:54 -0600 Subject: [PATCH 2/4] rename sharp macros to tag macros --- docs/language/api.rst | 16 +++++----- docs/tutorial.rst | 4 +-- hy/compiler.py | 22 +++++++------- hy/completer.py | 10 +++---- hy/core/macros.hy | 2 +- hy/lex/parser.py | 2 +- hy/macros.py | 26 ++++++++-------- ...est_sharp_macros.py => test_tag_macros.py} | 4 +-- .../{sharp_macros.hy => tag_macros.hy} | 30 +++++++++---------- tests/test_lex.py | 6 ++-- 10 files changed, 61 insertions(+), 61 deletions(-) rename tests/macros/{test_sharp_macros.py => test_tag_macros.py} (82%) rename tests/native_tests/{sharp_macros.hy => tag_macros.hy} (74%) diff --git a/docs/language/api.rst b/docs/language/api.rst index f36cbd5..0058c7d 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -779,19 +779,19 @@ For example, 42 -defsharp +deftag -------- .. 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``. It is called with the syntax ``#tag FORM``, where ``tag`` is the name of the macro, and ``FORM`` is any form. The ``tag`` is often only one character, but it can be any symbol. .. code-block:: clj - => (defsharp ♣ [expr] `[~expr ~expr]) + => (deftag ♣ [expr] `[~expr ~expr]) at 0x7f76d0271158> => #♣ 5 [5, 5] @@ -801,13 +801,13 @@ is any form. The ``tag`` is often only one character, but it can be any symbol. => x 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))``. -The syntax for calling sharp 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 +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, tag macros 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 --- @@ -1727,7 +1727,7 @@ will be 4 (``1+1 + 1+1``). .. 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: .. code-block:: clj diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 030a136..ceeab77 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -582,13 +582,13 @@ elements, so by the time program started executing, it actually reads: (+ 1 2 3) 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 is typically +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 out of characters that soon: .. code-block:: clj - => (defsharp ↻ [code] + => (deftag ↻ [code] ... (setv op (last code) params (list (butlast code))) ... `(~op ~@params)) => #↻(1 2 3 +) diff --git a/hy/compiler.py b/hy/compiler.py index d9c11b1..437495d 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -13,7 +13,7 @@ from hy.lex.parser import hy_symbol_mangle import hy.macros from hy._compat import ( 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 traceback @@ -2465,20 +2465,20 @@ class HyASTCompiler(object): return ret - @builds("defsharp") + @builds("deftag") @checkargs(min=2) - def compile_sharp_macro(self, expression): + def compile_tag_macro(self, expression): expression.pop(0) name = expression.pop(0) 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): raise HyTypeError(name, ("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) new_expression = HyExpression([ - HyExpression([HySymbol("hy.macros.sharp"), name]), + HyExpression([HySymbol("hy.macros.tag"), name]), HyExpression([HySymbol("fn")] + expression), ]).replace(expression) @@ -2486,19 +2486,19 @@ class HyASTCompiler(object): return ret - @builds("dispatch_sharp_macro") + @builds("dispatch_tag_macro") @checkargs(exact=2) - def compile_dispatch_sharp_macro(self, expression): - expression.pop(0) # dispatch-sharp-macro + def compile_dispatch_tag_macro(self, expression): + expression.pop(0) # dispatch-tag-macro tag = expression.pop(0) if not type(tag) == HyString: raise HyTypeError( tag, - "Trying to expand a sharp macro using `{0}' instead " + "Trying to expand a tag macro using `{0}' instead " "of string".format(type(tag).__name__), ) tag = HyString(hy_symbol_mangle(str(tag))).replace(tag) - expr = sharp_macroexpand(tag, expression.pop(0), self) + expr = tag_macroexpand(tag, expression.pop(0), self) return self.compile(expr) @builds("eval_and_compile") diff --git a/hy/completer.py b/hy/completer.py index 0a7faf1..0d9c906 100644 --- a/hy/completer.py +++ b/hy/completer.py @@ -40,11 +40,11 @@ class Completer(object): builtins.__dict__, hy.macros._hy_macros[None], namespace] - self.sharp_path = [hy.macros._hy_sharp[None]] + self.tag_path = [hy.macros._hy_tag[None]] if '__name__' in namespace: module_name = namespace['__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): # Borrowed from IPython's completer @@ -81,10 +81,10 @@ class Completer(object): matches.append(k) return matches - def sharp_matches(self, text): + def tag_matches(self, text): text = text[1:] matches = [] - for p in self.sharp_path: + for p in self.tag_path: for k in p.keys(): if isinstance(k, string_types): if k.startswith(text): @@ -93,7 +93,7 @@ class Completer(object): def complete(self, text, state): if text.startswith("#"): - matches = self.sharp_matches(text) + matches = self.tag_matches(text) elif "." in text: matches = self.attr_matches(text) else: diff --git a/hy/core/macros.hy b/hy/core/macros.hy index e1ca49a..f2bd8ea 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -230,7 +230,7 @@ (sys.exit ~retval)))) -(defsharp @ [expr] +(deftag @ [expr] (setv decorators (cut expr None -1) fndef (get expr -1)) `(with-decorator ~@decorators ~fndef)) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index e43d188..aa579e3 100755 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -204,7 +204,7 @@ def hash_other(p): st = p[0].getstr()[1:] str_object = HyString(st) 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") diff --git a/hy/macros.py b/hy/macros.py index 3da2bdb..b24735c 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -18,7 +18,7 @@ EXTRA_MACROS = [ ] _hy_macros = defaultdict(dict) -_hy_sharp = defaultdict(dict) +_hy_tag = defaultdict(dict) def macro(name): @@ -50,8 +50,8 @@ def macro(name): return _ -def sharp(name): - """Decorator to define a sharp macro called `name`. +def tag(name): + """Decorator to define a tag macro called `name`. This stores the macro `name` in the namespace for the module where it is defined. @@ -59,14 +59,14 @@ def sharp(name): 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 `defsharp` special form in the compiler. + This function is called from the `deftag` special form in the compiler. """ def _(fn): module_name = fn.__module__ if module_name.startswith("hy.core"): module_name = None - _hy_sharp[module_name][name] = fn + _hy_tag[module_name][name] = fn return fn return _ @@ -90,7 +90,7 @@ def require(source_module, target_module, if prefix: prefix += "." - for d in _hy_macros, _hy_sharp: + for d in _hy_macros, _hy_tag: for name, macro in d[source_module].items(): seen_names.add(name) if all_macros: @@ -210,19 +210,19 @@ def macroexpand_1(tree, compiler): return tree -def sharp_macroexpand(tag, tree, compiler): - """Expand the sharp macro "tag" with argument `tree`.""" +def tag_macroexpand(tag, tree, compiler): + """Expand the tag macro "tag" with argument `tree`.""" load_macros(compiler.module_name) - sharp_macro = _hy_sharp[compiler.module_name].get(tag) - if sharp_macro is None: + tag_macro = _hy_tag[compiler.module_name].get(tag) + if tag_macro is None: try: - sharp_macro = _hy_sharp[None][tag] + tag_macro = _hy_tag[None][tag] except KeyError: raise HyTypeError( tag, - "`{0}' is not a defined sharp macro.".format(tag) + "`{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) diff --git a/tests/macros/test_sharp_macros.py b/tests/macros/test_tag_macros.py similarity index 82% rename from tests/macros/test_sharp_macros.py rename to tests/macros/test_tag_macros.py index 788de2c..8a0493a 100644 --- a/tests/macros/test_sharp_macros.py +++ b/tests/macros/test_tag_macros.py @@ -7,10 +7,10 @@ from hy.compiler import HyTypeError, HyASTCompiler 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""" try: - macroexpand(tokenize("(dispatch_sharp_macro '- '())")[0], + macroexpand(tokenize("(dispatch_tag_macro '- '())")[0], HyASTCompiler(__name__)) except HyTypeError as e: assert "with the character `-`" in str(e) diff --git a/tests/native_tests/sharp_macros.hy b/tests/native_tests/tag_macros.hy similarity index 74% rename from tests/native_tests/sharp_macros.hy rename to tests/native_tests/tag_macros.hy index 937bc80..7fd4279 100644 --- a/tests/native_tests/sharp_macros.hy +++ b/tests/native_tests/tag_macros.hy @@ -5,34 +5,34 @@ (import [functools [wraps]]) -(defn test-sharp-macro [] - "Test a basic sharp macro" - (defsharp ^ [expr] +(defn test-tag-macro [] + "Test a basic tag macro" + (deftag ^ [expr] expr) (assert (= #^"works" "works"))) -(defn test-sharp-macro-expr [] +(defn test-tag-macro-expr [] "Test basic exprs like lists and arrays" - (defsharp n [expr] + (deftag n [expr] (get expr 1)) (assert (= #n[1 2] 2)) (assert (= #n(1 2) 2))) -(defn test-sharp-macro-override [] +(defn test-tag-macro-override [] "Test if we can override function symbols" - (defsharp + [n] + (deftag + [n] (+ n 1)) (assert (= #+2 3))) -(defn test-sharp-macros-macros [] - "Test if defsharp is actually a macro" - (defsharp t [expr] +(defn test-tag-macros-macros [] + "Test if deftag is actually a macro" + (deftag t [expr] `(, ~@expr)) (def a #t[1 2 3]) @@ -41,16 +41,16 @@ (assert (= (, 1 2 3) a))) -(defn test-sharp-macro-string-name [] - "Test if defsharp accepts a string as a macro name." +(defn test-tag-macro-string-name [] + "Test if deftag accepts a string as a macro name." - (defsharp "." [expr] + (deftag "." [expr] expr) (assert (= #."works" "works"))) -(defn test-builtin-decorator-sharp [] +(defn test-builtin-decorator-tag [] (defn increment-arguments [func] "Increments each argument passed to the decorated function." ((wraps func) @@ -72,7 +72,7 @@ (assert (= "foo" (. foo --name--))) (assert (= "Bar." (. foo --doc--))) - ;; We can use the #@ sharp macro to apply more than one decorator + ;; We can use the #@ tag macro to apply more than one decorator #@(increment-arguments increment-arguments (defn double-foo [&rest args &kwargs kwargs] diff --git a/tests/test_lex.py b/tests/test_lex.py index 1bfbd9f..33162e7 100644 --- a/tests/test_lex.py +++ b/tests/test_lex.py @@ -303,10 +303,10 @@ def test_complex(): assert entry == HySymbol("j") -def test_sharp_macro(): - """Ensure sharp macros are handled properly""" +def test_tag_macro(): + """Ensure tag macros are handled properly""" entry = tokenize("#^()") - assert entry[0][0] == HySymbol("dispatch_sharp_macro") + assert entry[0][0] == HySymbol("dispatch_tag_macro") assert entry[0][1] == HyString("^") assert len(entry[0]) == 3 From 901cfbda13217ddf6f01a65dc6f882180e8b17dd Mon Sep 17 00:00:00 2001 From: gilch Date: Thu, 22 Jun 2017 10:57:26 -0600 Subject: [PATCH 3/4] add tag macros to NEWS --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 35d4bff..e96cd69 100644 --- a/NEWS +++ b/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 [ Language Changes ] From b7a0c2abf02aafd3b9f8a66479a9430d26d02a7c Mon Sep 17 00:00:00 2001 From: gilch Date: Thu, 22 Jun 2017 11:09:01 -0600 Subject: [PATCH 4/4] add tag macro tests --- tests/native_tests/tag_macros.hy | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/native_tests/tag_macros.hy b/tests/native_tests/tag_macros.hy index 7fd4279..ba586f6 100644 --- a/tests/native_tests/tag_macros.hy +++ b/tests/native_tests/tag_macros.hy @@ -13,6 +13,51 @@ (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]