From 7c36fe9a145bcabf48ecbb6eaa8962fba625ab6d Mon Sep 17 00:00:00 2001 From: Tuukka Turto Date: Sat, 3 Oct 2015 13:24:43 +0300 Subject: [PATCH 01/40] Add section about macros in tutorial relates #926 --- docs/tutorial.rst | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index fc5e3ff..cf76538 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -503,6 +503,64 @@ In Hy: address (models.TextField) notes (models.TextField)]) +Macros +====== + +One really powerful feature of Hy are macros. They are small functios that are +used to generate code (or data). When program written in Hy is started, the +macros are executed and their output is placed in program source. After this, +the program starts executing normally. Very simple example: + +.. code-block:: clj + + => (defmacro hello [person] + ... `(print "Hello there," ~person)) + => (Hello "Tuukka") + Hello there, Tuukka + +The thing to notice here is that hello macro doesn't output anything on +screen. Instead it creates piece of code that is then executed and prints on +screen. Macro writes a piece of program that looks like this (provided that +we used "Tuukka" as parameter: + +.. code-block:: clj + + (print "Hello there," Tuukka) + +We can also manipulate code with macros: + +.. code-block:: clj + + => (defmacro rev [code] + ... (let [op (last code) params (list (butlast code))] + ... `(~op ~@params))) + => (rev (1 2 3 +)) + 6 + +The code that was generated with this macro just switched around some the +elements, so by the time program started executing, it actually red: + +.. code-block:: clj + + (+ 1 2 3) + +Sometimes it's nice to have a very short name for macro that doesn't take much +space or use extra parentheses. Reader macros can be pretty useful in these +situations (and since Hy operates well with unicode, we aren't running out of +characters that soon): + +.. code-block:: clj + + => (defreader ↻ [code] + ... (let [op (last code) params (list (butlast code))] + ... `(~op ~@params))) + => #↻(1 2 3 +) + 6 + +Macros are useful when one wished to extend the Hy or write their own +language on top of that. Many features of Hy are macros, like ``when``, +``cond`` and ``->``. + Hy <-> Python interop ===================== From 9e5f881958bdfd72484e2eeb147926ed6dc00587 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Tue, 13 Oct 2015 16:48:23 -0500 Subject: [PATCH 02/40] Give an error when &kwonly or &kwargs is used in a macro definition (closes #959) --- hy/compiler.py | 3 +++ tests/native_tests/native_macros.hy | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/hy/compiler.py b/hy/compiler.py index 4574a3f..1a2a4f0 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -2371,6 +2371,9 @@ class HyASTCompiler(object): raise HyTypeError(name, ("received a `%s' instead of a symbol " "for macro name" % type(name).__name__)) name = HyString(name).replace(name) + for kw in ("&kwonly", "&kwargs"): + if kw in expression[0]: + raise HyTypeError(name, "macros cannot use %s" % kw) new_expression = HyExpression([ HySymbol("with_decorator"), HyExpression([HySymbol("hy.macros.macro"), name]), diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 0d10609..5634c98 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -1,3 +1,5 @@ +(import [hy.errors [HyTypeError]]) + (defmacro rev [&rest body] "Execute the `body` statements in reverse" (quasiquote (do (unquote-splice (list (reversed body)))))) @@ -48,6 +50,20 @@ (defmacro bar [x y] (foo x y)) +(defn test-macro-kw [] + "NATIVE: test that an error is raised when &kwonly or &kwargs is used in a macro" + (try + (eval '(defmacro f [&kwonly a b])) + (except [e HyTypeError] + (assert (= e.message "macros cannot use &kwonly"))) + (else (assert false))) + + (try + (eval '(defmacro f [&kwargs kw])) + (except [e HyTypeError] + (assert (= e.message "macros cannot use &kwargs"))) + (else (assert false)))) + (defn test-fn-calling-macro [] "NATIVE: test macro calling a plain function" (assert (= 3 (bar 1 2)))) From 7e13bb4e5f142d2fbfaa03f88ba1263978adc930 Mon Sep 17 00:00:00 2001 From: Matthew Egan Odendahl Date: Fri, 15 Apr 2016 20:33:42 -0600 Subject: [PATCH 03/40] add as-> macro --- hy/core/macros.hy | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index d39aabe..dc3c316 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -30,6 +30,15 @@ [hy.models.symbol [HySymbol]] [hy._compat [PY33 PY34]]) +(defmacro as-> [head name &rest rest] + "Becomes ordered assignments to the provided name. The previous result + is thus available in each form. Returns the final result, and leaves the name + bound to it in the local scope. This behaves like the threading macros, but + requires you to specify the threading point per form via the name." + `(do (setv + ~name ~head + ~@(interleave (repeat name) rest)) + ~name)) (defmacro with [args &rest body] "shorthand for nested with* loops: From 02d5b0ccf7f2e8ed93fa8cbd30dccf5c708fdde4 Mon Sep 17 00:00:00 2001 From: "K.Adam White" Date: Mon, 9 May 2016 13:58:43 -0400 Subject: [PATCH 04/40] Include original XKCD title text in comic image When I see an XKCD comic, or an adaptation thereof, my first instinct is to look for title text. Not finding any, this proposes adding the original title text back in; if a good Hy-specific adaptation is proposed that'd be even better. And no, this isn't important :) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c1eb38..a487982 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Hy [![Version](https://img.shields.io/pypi/v/hy.svg)](https://pypi.python.org/pypi/hy) [![Coverage Status](https://img.shields.io/coveralls/hylang/hy/master.svg)](https://coveralls.io/r/hylang/hy) -[![XKCD #224](https://raw.github.com/hylang/shyte/18f6925e08684b0e1f52b2cc2c803989cd62cd91/imgs/xkcd.png)](https://xkcd.com/224/) +XKCD #224 Lisp and Python should love each other. Let's make it happen. [Try it](http://try-hy.appspot.com/). From d1fd483e591ca09fb3b111f4b796dc21b055d78c Mon Sep 17 00:00:00 2001 From: Matthew Egan Odendahl Date: Thu, 12 May 2016 16:41:26 -0600 Subject: [PATCH 05/40] minor doc fix --- docs/style-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/style-guide.rst b/docs/style-guide.rst index b577d79..40fa86a 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -105,7 +105,7 @@ Layout & Indentation .. code-block:: clj - (let [foo (bar)] + (let [foo (bar) qux (baz)] (foo qux)) From 35ea55caaf96831e9a8097ca4212247aad80b3a4 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Sun, 15 May 2016 15:08:32 +0200 Subject: [PATCH 06/40] Update readthedocs domains From the announcement : > Starting today, Read the Docs will start hosting projects from > subdomains on the domain readthedocs.io, instead of on readthedocs.org. [...] > Projects will automatically be redirected, and this redirect will > remain in place for the foreseeable future. Still, you should plan on > updating links to your documentation after the new domain goes live. --- docs/hacking.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hacking.rst b/docs/hacking.rst index b902a68..e7c8cf5 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -32,7 +32,7 @@ Do this: $ . venv/bin/activate - or use `virtualenvwrapper `_ + or use `virtualenvwrapper `_ to create and manage your virtual environment:: $ mkvirtualenv hy @@ -63,7 +63,7 @@ Test! ===== Tests are located in ``tests/``. We use `nose -`_. +`_. To run the tests:: From c6ad179bbb07ca69534f56b64057019633a31e76 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Sun, 15 May 2016 15:09:17 +0200 Subject: [PATCH 07/40] Use HTTPS for *.readthedocs.io --- docs/hacking.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hacking.rst b/docs/hacking.rst index e7c8cf5..4042208 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -32,7 +32,7 @@ Do this: $ . venv/bin/activate - or use `virtualenvwrapper `_ + or use `virtualenvwrapper `_ to create and manage your virtual environment:: $ mkvirtualenv hy From 8e8b12fd8ed8524a9484c86c5b3aeb88fab32c18 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 16 May 2016 14:57:56 -0700 Subject: [PATCH 08/40] Add Jakub Wilk to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index e4a45bc..e88a417 100644 --- a/AUTHORS +++ b/AUTHORS @@ -69,3 +69,4 @@ * Andrew Savchyn * Lev Kravinsky * Luna Lunapiena +* Jakub Wilk From c96d911ee781c132992af3e4d41e2321e52c27fd Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Wed, 8 Jun 2016 00:45:12 +0200 Subject: [PATCH 09/40] Fix typo (#1073) --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 01ba87a..f9c2ec5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -36,7 +36,7 @@ Pull requests are great! We love them; here is a quick guide: + Try sticking to the 50 character limit for the first line of Git commit messages. - + For more detail/explainations, follow this up with a blank line and + + For more detail/explanations, follow this up with a blank line and continue describing the commit in detail. - Finally, add yourself to the AUTHORS file (as a separate commit): you From 313dff554808f24c06f20a3bb7c38ae02b4493c1 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Sat, 11 Jun 2016 01:04:07 +0200 Subject: [PATCH 10/40] Fix typo --- docs/language/core.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language/core.rst b/docs/language/core.rst index b3bb65f..80b2c0e 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -141,7 +141,7 @@ is ``True``, the function prints Python code instead. print('Hello World!') -.. _emtpy?-fn: +.. _empty?-fn: empty? ------ From e0ecaca315cdc061905e1464e5245e1c13a96f40 Mon Sep 17 00:00:00 2001 From: Matthew Egan Odendahl Date: Sun, 19 Jun 2016 21:14:16 -0600 Subject: [PATCH 11/40] Clarify as-> docstring --- hy/core/macros.hy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index dc3c316..e6c8e16 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -31,10 +31,11 @@ [hy._compat [PY33 PY34]]) (defmacro as-> [head name &rest rest] - "Becomes ordered assignments to the provided name. The previous result - is thus available in each form. Returns the final result, and leaves the name - bound to it in the local scope. This behaves like the threading macros, but - requires you to specify the threading point per form via the name." + "Expands to sequence of assignments to the provided name, starting with head. + The previous result is thus available in the subsequent form. Returns the + final result, and leaves the name bound to it in the local scope. This behaves + much like the other threading macros, but requires you to specify the threading + point per form via the name instead of always the first or last arument." `(do (setv ~name ~head ~@(interleave (repeat name) rest)) From 93a66c7d5ecfce75601cd5d7d2921894141c90d4 Mon Sep 17 00:00:00 2001 From: John Clover Date: Fri, 24 Jun 2016 15:24:20 -0700 Subject: [PATCH 12/40] Remove redundant word (#1077) _a_ or _some_ would work here, I opted to keep _a_ --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f455bd3..7f9b4eb 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -267,7 +267,7 @@ In Hy, you would do: [true (print "That variable is jussssst right!")]) -What you'll notice is that ``cond`` switches off between a some statement +What you'll notice is that ``cond`` switches off between a statement that is executed and checked conditionally for true or falseness, and then a bit of code to execute if it turns out to be true. You'll also notice that the ``else`` is implemented at the end simply by checking From ff4ba5b103c1b75478cb532d5bc139ecd51338f2 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Fri, 1 Jul 2016 16:44:12 +0200 Subject: [PATCH 13/40] Fix typos --- NEWS | 2 +- hy/importer.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index ece2092..319e391 100644 --- a/NEWS +++ b/NEWS @@ -280,7 +280,7 @@ Changes from Hy 0.9.10 * Clarified rest / cdr, cleaned up require * Make with return the last expression from its branch * Fix yielding to not suck (#151) - * Make assoc accept multiple values, also added a even/odd check for + * Make assoc accept multiple values, also added an even/odd check for checkargs * Added ability to parse doc strings set in defclass declarations, * Provide bin scripts for both Windows and *nix diff --git a/hy/importer.py b/hy/importer.py index 8fcbd27..6eca35a 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -45,12 +45,12 @@ def ast_compile(ast, filename, mode): def import_buffer_to_hst(buf): - """Import content from buf and return an Hy AST.""" + """Import content from buf and return a Hy AST.""" return tokenize(buf + "\n") def import_file_to_hst(fpath): - """Import content from fpath and return an Hy AST.""" + """Import content from fpath and return a Hy AST.""" try: with open(fpath, 'r', encoding='utf-8') as f: return import_buffer_to_hst(f.read()) From 6dc12dd7038a913d890870fd91a6c928b3f67e7a Mon Sep 17 00:00:00 2001 From: Vinh Tu Date: Fri, 1 Jul 2016 22:59:56 +0100 Subject: [PATCH 14/40] Add import_file_to_globals for use in emacs inferior lisp mode --- hy/importer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hy/importer.py b/hy/importer.py index 6eca35a..8418b7c 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -89,6 +89,15 @@ def import_file_to_module(module_name, fpath): return mod +def import_file_to_globals(env, module_name, fpath): + """ Import content from fpath and puts it into the dict provided + (e.g., for use in a REPL) + """ + mod = import_file_to_module(module_name, fpath) + for k, v in mod.__dict__.items(): + env[k] = v + + def import_buffer_to_module(module_name, buf): try: _ast = import_buffer_to_ast(buf, module_name) From d384580de1a427f990b34dcccb42ce43e63adc6a Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Thu, 7 Jul 2016 11:24:04 -0500 Subject: [PATCH 15/40] Disallow &key in macros --- hy/compiler.py | 2 +- tests/native_tests/native_macros.hy | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 99c36d1..677dab4 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -2428,7 +2428,7 @@ class HyASTCompiler(object): raise HyTypeError(name, ("received a `%s' instead of a symbol " "for macro name" % type(name).__name__)) name = HyString(name).replace(name) - for kw in ("&kwonly", "&kwargs"): + for kw in ("&kwonly", "&kwargs", "&key"): if kw in expression[0]: raise HyTypeError(name, "macros cannot use %s" % kw) new_expression = HyExpression([ diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 8c2f602..eca4538 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -51,7 +51,7 @@ (foo x y)) (defn test-macro-kw [] - "NATIVE: test that an error is raised when &kwonly or &kwargs is used in a macro" + "NATIVE: test that an error is raised when &kwonly, &kwargs, or &key is used in a macro" (try (eval '(defmacro f [&kwonly a b])) (except [e HyTypeError] @@ -62,6 +62,12 @@ (eval '(defmacro f [&kwargs kw])) (except [e HyTypeError] (assert (= e.message "macros cannot use &kwargs"))) + (else (assert false))) + + (try + (eval '(defmacro f [&key {"kw" "xyz"}])) + (except [e HyTypeError] + (assert (= e.message "macros cannot use &key"))) (else (assert false)))) (defn test-fn-calling-macro [] From f1684e63068658947577cae96133b436cd9bccc9 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Date: Sat, 6 Aug 2016 20:25:07 -0500 Subject: [PATCH 16/40] Add doc and related link to good-first-bug tag (#1086) Fixes #1031 --- CONTRIBUTING.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f9c2ec5..cf3ddf6 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -7,7 +7,9 @@ helps in making Hy more awesome. Pull requests are great! We love them; here is a quick guide: - `Fork the repo`_ and create a topic branch for a feature/fix. Avoid - making changes directly on the master branch. + making changes directly on the master branch. If you would like to + contribute but don't know how to begin, the `good-first-bug`_ label + of the `issue tracker`_ is the place to go. (If you're new to Git: `Start Here`_) - All incoming features should be accompanied with tests. @@ -93,4 +95,5 @@ http://contributor-covenant.org/version/1/1/0/. .. _Contributor Covenant: http://contributor-covenant.org .. _issue tracker: https://github.com/hylang/hy/issues .. _Fork the Repo: https://help.github.com/articles/fork-a-repo/ -.. _Start Here: http://rogerdudler.github.io/git-guide/) \ No newline at end of file +.. _Start Here: http://rogerdudler.github.io/git-guide/) +.. _good-first-bug: http://github.com/hylang/hy/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-bug From 6a452d70a46f00fa521cc543e321d8efa391baa8 Mon Sep 17 00:00:00 2001 From: Michael Hunsinger Date: Sat, 20 Aug 2016 02:35:02 -0600 Subject: [PATCH 17/40] Added documentation for walk, postwalk and prewalk --- docs/contrib/index.rst | 5 ++- docs/contrib/walk.rst | 94 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 docs/contrib/walk.rst diff --git a/docs/contrib/index.rst b/docs/contrib/index.rst index 612ccdd..ed75add 100644 --- a/docs/contrib/index.rst +++ b/docs/contrib/index.rst @@ -7,8 +7,9 @@ Contents: .. toctree:: :maxdepth: 3 + alias anaphoric + flow loop multi - flow - alias + walk diff --git a/docs/contrib/walk.rst b/docs/contrib/walk.rst new file mode 100644 index 0000000..01e2bc0 --- /dev/null +++ b/docs/contrib/walk.rst @@ -0,0 +1,94 @@ +==== +walk +==== + +.. versionadded:: 0.11.0 + +Functions +========= + +.. _walk: + +walk +----- + +Usage: `(walk inner outer form)` + +``walk`` traverses ``form``, an arbitrary data structure. Applies +``inner`` to each element of form, building up a data structure of the +same type. Applies ``outer`` to the result. + +Example: + +.. code-block:: hy + + => (import [hy.contrib.walk [walk]]) + => (setv a '(a b c d e f)) + => (walk ord identity a) + (97 98 99 100 101 102) + => (walk ord first a) + 97 + +postwalk +--------- + +.. _postwalk: + +Usage: `(postwalk f form)` + +Performs depth-first, post-order traversal of ``form``. Calls ``f`` on +each sub-form, uses ``f`` 's return value in place of the original. + +.. code-block:: hy + + => (import [hy.contrib.walk [postwalk]]) + => (def trail '([1 2 3] [4 [5 6 [7]]])) + => (defn walking [x] + (print "Walking:" x) + x ) + => (postwalk walking trail) + Walking: 1 + Walking: 2 + Walking: 3 + Walking: (1 2 3) + Walking: 4 + Walking: 5 + Walking: 6 + Walking: 7 + Walking: (7) + Walking: (5 6 [7]) + Walking: (4 [5 6 [7]]) + Walking: ([1 2 3] [4 [5 6 [7]]]) + ([1 2 3] [4 [5 6 [7]]]) + +prewalk +-------- + +.. _prewalk: + +Usage: `(prewalk f form)` + +Performs depth-first, pre-order traversal of ``form``. Calls ``f`` on +each sub-form, uses ``f`` 's return value in place of the original. + +.. code-block:: hy + + => (import [hy.contrib.walk [prewalk]]) + => (def trail '([1 2 3] [4 [5 6 [7]]])) + => (defn walking [x] + (print "Walking:" x) + x ) + => (prewalk walking trail) + Walking: ([1 2 3] [4 [5 6 [7]]]) + Walking: [1 2 3] + Walking: 1 + Walking: 2 + Walking: 3 + Walking: [4 [5 6 [7]]] + Walking: 4 + Walking: [5 6 [7]] + Walking: 5 + Walking: 6 + Walking: [7] + Walking: 7 + ([1 2 3] [4 [5 6 [7]]]) From b94211251c647a5622e0dcf35f2f19a8bb799882 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Sun, 4 Sep 2016 22:35:46 +0200 Subject: [PATCH 18/40] Use HTTPS for Wikipedia URLs --- docs/contrib/anaphoric.rst | 2 +- docs/contrib/loop.rst | 2 +- docs/language/api.rst | 2 +- docs/language/internals.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/contrib/anaphoric.rst b/docs/contrib/anaphoric.rst index cce39e0..51ece00 100644 --- a/docs/contrib/anaphoric.rst +++ b/docs/contrib/anaphoric.rst @@ -11,7 +11,7 @@ concise and easy to read. deliberately captures some form supplied to the macro which may be referred to by an anaphor (an expression referring to another). - -- Wikipedia (http://en.wikipedia.org/wiki/Anaphoric_macro) + -- Wikipedia (https://en.wikipedia.org/wiki/Anaphoric_macro) To use these macros you need to require the hy.contrib.anaphoric module like so: diff --git a/docs/contrib/loop.rst b/docs/contrib/loop.rst index 9968aab..9ff80a2 100644 --- a/docs/contrib/loop.rst +++ b/docs/contrib/loop.rst @@ -24,7 +24,7 @@ tail-call optimization (TCO) in their Hy code. position to be implemented as efficiently as goto statements, thus allowing efficient structured programming. - -- Wikipedia (http://en.wikipedia.org/wiki/Tail_call) + -- Wikipedia (https://en.wikipedia.org/wiki/Tail_call) Macros ====== diff --git a/docs/language/api.rst b/docs/language/api.rst index c96e0b6..7923cee 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -22,7 +22,7 @@ languages. string. For example, ``foo`` will become ``FOO``. * UTF-8 entities will be encoded using - `punycode `_ and prefixed with + `punycode `_ and prefixed with ``hy_``. For instance, ``⚘`` will become ``hy_w7h``, ``♥`` will become ``hy_g6h``, and ``i♥u`` will become ``hy_iu_t0x``. diff --git a/docs/language/internals.rst b/docs/language/internals.rst index d543847..c4b9db6 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -170,7 +170,7 @@ Cons Cells cells`_. Cons cells are especially useful to mimic features of "usual" LISP variants such as Scheme or Common Lisp. -.. _cons cells: http://en.wikipedia.org/wiki/Cons +.. _cons cells: https://en.wikipedia.org/wiki/Cons A cons cell is a 2-item object, containing a ``car`` (head) and a ``cdr`` (tail). In some Lisp variants, the cons cell is the fundamental From a60b749d3eaae764648a498297b2afad069e3a00 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 20 Sep 2016 13:05:52 -0700 Subject: [PATCH 19/40] Make unary + call __pos__ Fixes #1109. __pos__ doesn't work on strings, lists, or tuples, so I've removed some tests that required (= (+ "a") "a") etc. --- hy/compiler.py | 28 ++++++++++++++++++++-------- hy/core/shadow.hy | 2 +- tests/native_tests/mathematics.hy | 12 ++++++++++++ tests/native_tests/shadow.hy | 4 ---- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index ca8c0e9..d663d58 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1910,7 +1910,6 @@ class HyASTCompiler(object): col_offset=child.start_column) return ret - @builds("+") @builds("*") @builds("/") @builds("//") @@ -1918,8 +1917,7 @@ class HyASTCompiler(object): if len(expression) > 2: return self.compile_maths_expression(expression) else: - id_op = {"+": HyInteger(0), "*": HyInteger(1), "/": HyInteger(1), - "//": HyInteger(1)} + id_op = {"*": HyInteger(1), "/": HyInteger(1), "//": HyInteger(1)} op = expression.pop(0) arg = expression.pop(0) if expression else id_op[op] @@ -1930,20 +1928,34 @@ class HyASTCompiler(object): ]).replace(expression) return self.compile_maths_expression(expr) - @builds("-") - @checkargs(min=1) - def compile_maths_expression_sub(self, expression): + def compile_maths_expression_additive(self, expression): if len(expression) > 2: return self.compile_maths_expression(expression) else: - arg = expression[1] + op = {"+": ast.UAdd, "-": ast.USub}[expression.pop(0)]() + arg = expression.pop(0) ret = self.compile(arg) - ret += ast.UnaryOp(op=ast.USub(), + ret += ast.UnaryOp(op=op, operand=ret.force_expr, lineno=arg.start_line, col_offset=arg.start_column) return ret + @builds("+") + def compile_maths_expression_add(self, expression): + if len(expression) == 1: + # Nullary + + return ast.Num(n=long_type(0), + lineno=expression.start_line, + col_offset=expression.start_column) + else: + return self.compile_maths_expression_additive(expression) + + @builds("-") + @checkargs(min=1) + def compile_maths_expression_sub(self, expression): + return self.compile_maths_expression_additive(expression) + @builds("+=") @builds("/=") @builds("//=") diff --git a/hy/core/shadow.hy b/hy/core/shadow.hy index b63219d..e66d075 100644 --- a/hy/core/shadow.hy +++ b/hy/core/shadow.hy @@ -30,7 +30,7 @@ (if (zero? count) (raise (TypeError "Need at least 1 argument to add/concatenate")) (if (= count 1) - (get args 0) + (operator.pos (get args 0)) (reduce operator.add args))))) diff --git a/tests/native_tests/mathematics.hy b/tests/native_tests/mathematics.hy index 58c5457..5ab90c4 100644 --- a/tests/native_tests/mathematics.hy +++ b/tests/native_tests/mathematics.hy @@ -28,6 +28,18 @@ (assert (= 0 (+))))) +(defn test-add-unary [] + "NATIVE: test that unary + calls __pos__" + + (defclass X [object] + [__pos__ (fn [self] "called __pos__")]) + (assert (= (+ (X)) "called __pos__")) + + ; Make sure the shadowed version works, too. + (setv f +) + (assert (= (f (X)) "called __pos__"))) + + (setv test_div (fn [] "NATIVE: Test division" (assert (= 25 (/ 100 2 2))) diff --git a/tests/native_tests/shadow.hy b/tests/native_tests/shadow.hy index 0ca08ba..ce578bf 100644 --- a/tests/native_tests/shadow.hy +++ b/tests/native_tests/shadow.hy @@ -8,13 +8,9 @@ (assert (= (x 1 2 3 4) 10)) (assert (= (x 1 2 3 4 5) 15)) ; with strings - (assert (= (x "a") - "a")) (assert (= (x "a" "b" "c") "abc")) ; with lists - (assert (= (x ["a"]) - ["a"])) (assert (= (x ["a"] ["b"] ["c"]) ["a" "b" "c"])))) From 4db667322c1869342704805dcb8b64bb4b2f7a97 Mon Sep 17 00:00:00 2001 From: Jakukyo Friel Date: Thu, 22 Sep 2016 08:32:35 +0800 Subject: [PATCH 20/40] Doc: Fix typo (missing quote) (#1096) --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 7f9b4eb..834332d 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -292,7 +292,7 @@ You can do the following: (if (try-some-thing) (do (print "this is if true") - (print "and why not, let's keep talking about how true it is!)) + (print "and why not, let's keep talking about how true it is!")) (print "this one's still simply just false")) You can see that we used ``do`` to wrap multiple statements. If you're From 35de9988714656625429b91da076473057e48e21 Mon Sep 17 00:00:00 2001 From: Jakukyo Friel Date: Thu, 22 Sep 2016 08:33:32 +0800 Subject: [PATCH 21/40] Docs: Fix rst markup in docs/contrib/loop.rst. (#1098) In RestrutruedText, double backtick quotes (instead of single backtick quotes in markdown) are used for code span. --- docs/contrib/loop.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contrib/loop.rst b/docs/contrib/loop.rst index 9ff80a2..aaef239 100644 --- a/docs/contrib/loop.rst +++ b/docs/contrib/loop.rst @@ -39,7 +39,7 @@ rebinds the variables set in the recursion point and sends code execution back to that recursion point. If ``recur`` is used in a non-tail position, an exception is raised. -Usage: `(loop bindings &rest body)` +Usage: ``(loop bindings &rest body)`` Example: From 53353c58f2fadc1063eb75110e94e14d244be986 Mon Sep 17 00:00:00 2001 From: "Zack M. Davis" Date: Wed, 21 Sep 2016 22:05:53 -0700 Subject: [PATCH 22/40] fix Hy on recent Pythons In issue #1111, @tianon reported that Hy didn't work with Python 3.6.0b1: trying to evaluate a simple expression at the REPL blew up with `TypeError: required field "is_async" missing from comprehension`. This was due to a grammar change (https://www.python.org/dev/peps/pep-0530/#grammar-updates) in the implementation (https://hg.python.org/cpython/rev/cf91a929b81a) of PEP 530, which we can easily accomodate. --- hy/compiler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hy/compiler.py b/hy/compiler.py index ca8c0e9..6fc1551 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1516,7 +1516,8 @@ class HyASTCompiler(object): gen.append(ast.comprehension( target=target, iter=gen_res.force_expr, - ifs=[])) + ifs=[], + is_async=False)) if cond.expr: gen[-1].ifs.append(cond.expr) From d81b71f2423b7fbfe38bc5ed27583c7d56cfe243 Mon Sep 17 00:00:00 2001 From: Jakukyo Friel Date: Thu, 22 Sep 2016 20:20:26 +0800 Subject: [PATCH 23/40] Doc: CLI: mention `--spy` only works in REPL mode. (#1097) --- docs/language/cli.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index 28bd067..ae9b620 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -35,7 +35,7 @@ Command Line Options .. cmdoption:: --spy - Print equivalent Python code before executing. For example:: + Print equivalent Python code before executing in REPL. For example:: => (defn salutationsnm [name] (print (+ "Hy " name "!"))) def salutationsnm(name): @@ -45,6 +45,7 @@ Command Line Options Hy YourName! => + `--spy` only works on REPL mode. .. versionadded:: 0.9.11 .. cmdoption:: --show-tracebacks From d4494151f4dcb454e15a297e3012c68b356ffb35 Mon Sep 17 00:00:00 2001 From: "Zack M. Davis" Date: Thu, 22 Sep 2016 23:24:14 -0700 Subject: [PATCH 24/40] experiment: can we convince Travis CI to test with nightly Python? The docs say that "nightly" is supported, but it's less clear exactly what our .travis.yml should say to make this happen. --- .travis.yml | 2 ++ tox.ini | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9f8dea7..8ab3a44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ matrix: include: - python: 3.5 env: TOXENV=py35 + - python: nightly + env: TOXENV=py36 env: - TOXENV=py27 - TOXENV=py33 diff --git a/tox.ini b/tox.ini index 2bea02d..0162794 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,pypy,py33,py34,py35,flake8 +envlist = py27,pypy,py33,py34,py35,py36,flake8 skipsdist = True [testenv] From 1d3142c8de003f037a674e36e847ee81402319f8 Mon Sep 17 00:00:00 2001 From: gilch Date: Sat, 24 Sep 2016 15:54:32 -0600 Subject: [PATCH 25/40] change quickstart install from PyPI to GitHub (#1116) * change Quickstart install from PyPI to GitHub --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 24bca21..7ee72e0 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -13,7 +13,7 @@ Quickstart 1. Create a `Virtual Python Environment `_. 2. Activate your Virtual Python Environment. -3. Install `hy from PyPI `_ with ``pip install hy``. +3. Install `hy from GitHub `_ with ``$ pip install git+https://github.com/hylang/hy.git``. 4. Start a REPL with ``hy``. 5. Type stuff in the REPL:: From 399d2bcf2116fdf0a453ed1cc1192e994b9d4a90 Mon Sep 17 00:00:00 2001 From: gilch Date: Sun, 25 Sep 2016 21:29:53 -0600 Subject: [PATCH 26/40] clarify documentation of import (#1121) * clarify documentation of import Add a comment showing the equivalent python of the long example, and demonstrate how to get `from` and `as` on the same line. * add python example comment in last import example --- docs/language/api.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 7923cee..fa09c82 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -953,11 +953,20 @@ that ``import`` can be used. (import [sys :as systest]) ;; You can list as many imports as you like of different types. + ;; + ;; Python: + ;; from tests.resources import kwtest, function_with_a_dash + ;; from os.path import exists, isdir as is_dir, isfile as is_file + ;; import sys as systest (import [tests.resources [kwtest function-with-a-dash]] - [os.path [exists isdir isfile]] + [os.path [exists + isdir :as dir? + isfile :as file?]] [sys :as systest]) ;; Import all module functions into current namespace + ;; + ;; Python: from sys import * (import [sys [*]]) From 0880610401ae1249cd434514ddc607a1f25705d8 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 26 Sep 2016 09:47:04 -0700 Subject: [PATCH 27/40] Don't sort or deduplicate the items in a HySet Fixes #1120. I also added hy.models._wrapper[set] so a macro can return an ordinary set in place of a HySet. --- hy/models/set.py | 11 ++++------- tests/lex/test_lex.py | 10 ++++++++++ tests/models/test_set.py | 2 +- tests/native_tests/language.hy | 1 + tests/native_tests/native_macros.hy | 3 +++ 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/hy/models/set.py b/hy/models/set.py index 3a81aed..fda7b52 100644 --- a/hy/models/set.py +++ b/hy/models/set.py @@ -18,19 +18,16 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from hy.models import _wrappers, wrap_value from hy.models.list import HyList -from functools import reduce class HySet(HyList): """ - Hy set (actually a list that pretends to be a set) + Hy set (just a representation of a set) """ - def __init__(self, items): - items = sorted(items) - items = list(reduce(lambda r, v: v in r and r or r+[v], items, [])) - super(HySet, self).__init__(items) - def __repr__(self): return "#{%s}" % (" ".join([repr(x) for x in self])) + +_wrappers[set] = lambda s: HySet(wrap_value(x) for x in s) diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index 4413a6b..81407cc 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -230,6 +230,16 @@ def test_sets(): HyExpression([HySymbol("baz"), HySymbol("quux")]) ])] + # Duplicate items in a literal set should be okay (and should + # be preserved). + objs = tokenize("#{1 2 1 1 2 1}") + assert objs == [HySet([HyInteger(n) for n in [1, 2, 1, 1, 2, 1]])] + assert len(objs[0]) == 6 + + # https://github.com/hylang/hy/issues/1120 + objs = tokenize("#{a 1}") + assert objs == [HySet([HySymbol("a"), HyInteger(1)])] + def test_nospace(): """ Ensure we can tokenize without spaces if we have to """ diff --git a/tests/models/test_set.py b/tests/models/test_set.py index ceea726..0462c2c 100644 --- a/tests/models/test_set.py +++ b/tests/models/test_set.py @@ -5,4 +5,4 @@ hyset = HySet([3, 1, 2, 2]) def test_set(): - assert hyset == [1, 2, 3] + assert hyset == [3, 1, 2, 2] diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 0300ffb..54b3e61 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -47,6 +47,7 @@ (defn test-sets [] "NATIVE: test sets work right" (assert (= #{1 2 3 4} (| #{1 2} #{3 4}))) + (assert (= (type #{1 2 3 4}) set)) (assert (= #{} (set)))) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index eca4538..788e52b 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -39,6 +39,9 @@ (defmacro a-dict [] {1 2}) (assert (= (a-dict) {1 2})) +(defmacro a-set [] #{1 2}) +(assert (= (a-set) #{1 2})) + (defmacro a-none []) (assert (= (a-none) None)) From a33f80b2ce45a0c2f633ab13dce79852ee6dfdbf Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 29 Sep 2016 09:18:04 -0700 Subject: [PATCH 28/40] Allow --version as a synonym for -v --version is a GNU standard that is also common for non-GNU programs, such as Python. --- hy/cmdline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index 195a5bc..35f75c6 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -274,7 +274,7 @@ def cmdline_handler(scriptname, argv): parser.add_argument("--spy", action="store_true", help="print equivalent Python code before executing") - parser.add_argument("-v", action="version", version=VERSION) + parser.add_argument("-v", "--version", action="version", version=VERSION) parser.add_argument("--show-tracebacks", action="store_true", help="show complete tracebacks for Hy exceptions") From 1711b1f5ad303a979df9bcb8d4e8b8cc9aea571d Mon Sep 17 00:00:00 2001 From: Bheesham Persaud Date: Tue, 4 Oct 2016 21:00:19 -0400 Subject: [PATCH 29/40] Add PHONY targets; wrap call to recursive make; add path to find. I've marked added the rest of the phony build targets; wrapped the recursive make call[0], and added the path specifier to the find command for compatibility with BSD find. [0]: https://www.gnu.org/software/make/manual/html_node/Recursion.html --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index ccf8456..40d8eaf 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ all: @echo "" docs: - make -C docs html + $(MAKE) -C docs html upload: r python setup.py sdist upload @@ -72,10 +72,10 @@ coveralls: clean: @find . -name "*.pyc" -exec rm {} \; - @find -name __pycache__ -delete + @find . -name __pycache__ -delete @${RM} -r -f .tox @${RM} -r -f dist @${RM} -r -f *.egg-info @${RM} -r -f docs/_build -.PHONY: docs +.PHONY: all docs upload full venv dev test tox flake clear d diff r python coveralls clean From 72ded830b61ebc060dc7c6d2c1d4bcde9ef796e2 Mon Sep 17 00:00:00 2001 From: Jorge Date: Sun, 9 Oct 2016 13:01:08 +0200 Subject: [PATCH 30/40] Remove invalid `)` from URL --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index cf3ddf6..0e01d57 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -95,5 +95,5 @@ http://contributor-covenant.org/version/1/1/0/. .. _Contributor Covenant: http://contributor-covenant.org .. _issue tracker: https://github.com/hylang/hy/issues .. _Fork the Repo: https://help.github.com/articles/fork-a-repo/ -.. _Start Here: http://rogerdudler.github.io/git-guide/) +.. _Start Here: http://rogerdudler.github.io/git-guide/ .. _good-first-bug: http://github.com/hylang/hy/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-bug From 108537a4e0d0c9b467ae116ffab344ec8b4ffbca Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 11 Oct 2016 13:31:22 -0700 Subject: [PATCH 31/40] Allow &rest after &optional, like Python --- hy/compiler.py | 10 ++-------- tests/compilers/test_ast.py | 2 ++ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 6e6e627..b6c6d27 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -503,19 +503,13 @@ class HyASTCompiler(object): for expr in exprs: if expr in ll_keywords: - if expr == "&rest" and lambda_keyword is None: - lambda_keyword = expr - elif expr == "&optional": + if expr == "&optional": if len(defaults) > 0: raise HyTypeError(expr, "There can only be &optional " "arguments or one &key argument") lambda_keyword = expr - elif expr == "&key": - lambda_keyword = expr - elif expr == "&kwonly": - lambda_keyword = expr - elif expr == "&kwargs": + elif expr in ("&rest", "&key", "&kwonly", "&kwargs"): lambda_keyword = expr else: raise HyTypeError(expr, diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 4898c40..31c90bd 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -397,6 +397,7 @@ def test_lambda_list_keywords_rest(): """ Ensure we can compile functions with lambda list keywords.""" can_compile("(fn (x &rest xs) (print xs))") cant_compile("(fn (x &rest xs &rest ys) (print xs))") + can_compile("(fn (&optional a &rest xs) (print xs))") def test_lambda_list_keywords_key(): @@ -410,6 +411,7 @@ def test_lambda_list_keywords_kwargs(): """ Ensure we can compile functions with &kwargs.""" can_compile("(fn (x &kwargs kw) (list x kw))") cant_compile("(fn (x &kwargs xs &kwargs ys) (list x xs ys))") + can_compile("(fn (&optional x &kwargs kw) (list x kw))") def test_lambda_list_keywords_kwonly(): From 4b0296d257be392068ed1ffbab037857fa8bbc38 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 11 Oct 2016 14:04:50 -0700 Subject: [PATCH 32/40] Don't check the type of arguments to `inc`, `odd?`, etc. This allows them to be used with numeric types that aren't built in, such as NumPy arrays. Because Python uses duck typing, there's generally no way to know in advance whether a given value will accept a given operator. Of course, things like `(inc "hello")` will still raise a `TypeError`, because so does `(+ "hello" 1)`. --- hy/core/language.hy | 11 ------- tests/native_tests/core.hy | 59 +++++++++++--------------------------- 2 files changed, 16 insertions(+), 54 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 07bc202..b7fce7e 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -39,10 +39,6 @@ (import [hy.lex [LexException PrematureEndOfInput tokenize]]) (import [hy.compiler [HyASTCompiler]]) -(defn _numeric-check [x] - (if (not (numeric? x)) - (raise (TypeError (.format "{0!r} is not a number" x))))) - (defn butlast [coll] "Returns coll except of last element." (drop-last 1 coll)) @@ -66,7 +62,6 @@ (defn dec [n] "Decrement n by 1" - (_numeric-check n) (- n 1)) (defn disassemble [tree &optional [codegen false]] @@ -170,7 +165,6 @@ (defn even? [n] "Return true if n is an even number" - (_numeric-check n) (= (% n 2) 0)) (defn every? [pred coll] @@ -239,7 +233,6 @@ (defn inc [n] "Increment n by 1" - (_numeric-check n) (+ n 1)) (defn instance? [klass x] @@ -325,7 +318,6 @@ (defn neg? [n] "Return true if n is < 0" - (_numeric-check n) (< n 0)) (defn none? [x] @@ -347,7 +339,6 @@ (defn odd? [n] "Return true if n is an odd number" - (_numeric-check n) (= (% n 2) 1)) (def -sentinel (object)) @@ -364,7 +355,6 @@ (defn pos? [n] "Return true if n is > 0" - (_numeric_check n) (> n 0)) (defn rest [coll] @@ -415,7 +405,6 @@ (defn zero? [n] "Return true if n is 0" - (_numeric_check n) (= n 0)) (defn read [&optional [from-file sys.stdin] diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 9e3ad52..ead3960 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -33,6 +33,11 @@ (defn assert-nil [x] (assert (is x nil))) +(defn assert-requires-num [f] + (for [x ["foo" [] None]] + (assert-true (try (do (f x) False) + (except [e [TypeError]] True))))) + (defn test-coll? [] "NATIVE: testing coll?" (assert-true (coll? [1 2 3])) @@ -66,12 +71,7 @@ (assert-equal 0 (dec 1)) (assert-equal -1 (dec 0)) (assert-equal 0 (dec (dec 2))) - (try (do (dec "foo") (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (dec []) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (dec None) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e)))))) + (assert-requires-num dec)) (defn test-setv [] "NATIVE: testing setv mutation" @@ -173,12 +173,7 @@ (assert-true (even? -2)) (assert-false (even? 1)) (assert-true (even? 0)) - (try (even? "foo") - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (even? []) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (even? None) - (except [e [TypeError]] (assert (in "not a number" (str e)))))) + (assert-requires-num even?)) (defn test-every? [] "NATIVE: testing the every? function" @@ -263,12 +258,11 @@ "NATIVE: testing the inc function" (assert-equal 3 (inc 2)) (assert-equal 0 (inc -1)) - (try (do (inc "foo") (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (inc []) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (inc None) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e)))))) + (assert-requires-num inc) + + (defclass X [object] + [__add__ (fn [self other] (.format "__add__ got {}" other))]) + (assert-equal (inc (X)) "__add__ got 1")) (defn test-instance [] "NATIVE: testing instance? function" @@ -394,24 +388,13 @@ (assert-true (neg? -2)) (assert-false (neg? 1)) (assert-false (neg? 0)) - (try (do (neg? "foo") (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (neg? []) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (neg? None) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e)))))) + (assert-requires-num neg?)) (defn test-zero [] "NATIVE: testing the zero? function" (assert-false (zero? -2)) (assert-false (zero? 1)) - (assert-true (zero? 0)) - (try (do (zero? "foo") (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (zero? []) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (zero? None) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e)))))) + (assert-true (zero? 0))) (defn test-none [] "NATIVE: testing for `is None`" @@ -463,12 +446,7 @@ (assert-true (odd? -3)) (assert-true (odd? 1)) (assert-false (odd? 0)) - (try (do (odd? "foo") (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (odd? []) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (odd? None) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e)))))) + (assert-requires-num odd?)) (defn test-partition [] "NATIVE: testing the partition function" @@ -500,12 +478,7 @@ (assert-true (pos? 2)) (assert-false (pos? -1)) (assert-false (pos? 0)) - (try (do (pos? "foo") (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (pos? []) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e))))) - (try (do (pos? None) (assert False)) - (except [e [TypeError]] (assert (in "not a number" (str e)))))) + (assert-requires-num pos?)) (defn test-remove [] "NATIVE: testing the remove function" From cd1d12565db3a645088f5a6f6517208c18c5b086 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 11 Oct 2016 15:48:40 -0700 Subject: [PATCH 33/40] Assertion-testing style edit --- tests/native_tests/core.hy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index ead3960..ee74b6f 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -35,8 +35,9 @@ (defn assert-requires-num [f] (for [x ["foo" [] None]] - (assert-true (try (do (f x) False) - (except [e [TypeError]] True))))) + (try (f x) + (except [TypeError] True) + (else (assert False))))) (defn test-coll? [] "NATIVE: testing coll?" From 0eb407676828fd6a24e9aafc5b10988026548954 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 11 Oct 2016 20:14:14 -0700 Subject: [PATCH 34/40] Require Python 3 for neg? and pos? type tests Python 2 allows 2 < "hello" and the like. --- tests/native_tests/core.hy | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index ee74b6f..0bf1d4f 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -19,6 +19,8 @@ ;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ;; DEALINGS IN THE SOFTWARE. +(import [hy._compat [PY3]]) + ;;;; some simple helpers (defn assert-true [x] @@ -389,7 +391,8 @@ (assert-true (neg? -2)) (assert-false (neg? 1)) (assert-false (neg? 0)) - (assert-requires-num neg?)) + (when PY3 + (assert-requires-num neg?))) (defn test-zero [] "NATIVE: testing the zero? function" @@ -479,7 +482,8 @@ (assert-true (pos? 2)) (assert-false (pos? -1)) (assert-false (pos? 0)) - (assert-requires-num pos?)) + (when PY3 + (assert-requires-num pos?))) (defn test-remove [] "NATIVE: testing the remove function" From 34304c41c7063bc3c9f25a4d150a0d9ead1eb2f2 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 12 Oct 2016 17:11:46 -0500 Subject: [PATCH 35/40] Test Python 3.6 apart from nightly (#1133) * Test Python 3.6 apart from nightly (ref. #1113) change to `3.6-dev` --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8ab3a44..bd99240 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ matrix: include: - python: 3.5 env: TOXENV=py35 - - python: nightly + - python: 3.6-dev env: TOXENV=py36 env: - TOXENV=py27 From 23c457cee576db84cbef14735d3bb88221722a64 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Wed, 12 Oct 2016 17:11:44 -0700 Subject: [PATCH 36/40] Generate hy.version using `git describe` --- .gitignore | 1 + hy/__init__.py | 6 +++++- hy/version.py | 23 ----------------------- setup.py | 19 +++++++------------ 4 files changed, 13 insertions(+), 36 deletions(-) delete mode 100644 hy/version.py diff --git a/.gitignore b/.gitignore index d3a4b8c..f1cb8d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/hy/version.py *.pyc *swp *hy*egg* diff --git a/hy/__init__.py b/hy/__init__.py index 8b2bbc4..4d5ef77 100644 --- a/hy/__init__.py +++ b/hy/__init__.py @@ -19,7 +19,11 @@ # DEALINGS IN THE SOFTWARE. -from hy.version import __version__, __appname__ # NOQA +__appname__ = 'hy' +try: + from hy.version import __version__ +except ImportError: + __version__ = 'unknown' from hy.models.expression import HyExpression # NOQA diff --git a/hy/version.py b/hy/version.py deleted file mode 100644 index a4671ab..0000000 --- a/hy/version.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2013 Paul Tagliamonte -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - - -__appname__ = "hy" -__version__ = "0.11.0" diff --git a/setup.py b/setup.py index d2d8efd..17587d4 100755 --- a/setup.py +++ b/setup.py @@ -22,24 +22,19 @@ import os import re import sys +import subprocess from setuptools import find_packages, setup PKG = "hy" VERSIONFILE = os.path.join(PKG, "version.py") -verstr = "unknown" try: - verstrline = open(VERSIONFILE, "rt").read() -except EnvironmentError: - pass # Okay, there is no version file. -else: - VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" - mo = re.search(VSRE, verstrline, re.M) - if mo: - __version__ = mo.group(1) - else: - msg = "if %s.py exists, it is required to be well-formed" % VERSIONFILE - raise RuntimeError(msg) + __version__ = subprocess.check_output([ + "git", "describe", "--tags", "--dirty"]).decode('ASCII').strip() + with open(VERSIONFILE, "wt") as o: + o.write("__version__ = {!r}\n".format(__version__)) +except subprocess.CalledProcessError: + __version__ = "unknown" long_description = """Hy is a Python <--> Lisp layer. It helps make things work nicer, and lets Python and the Hy lisp variant play From ec6e2647535389439fc8ab18060b7a6c9091a3f0 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 13 Oct 2016 13:32:13 -0700 Subject: [PATCH 37/40] Allow calling setup.py from a subdirectory --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 17587d4..4d0a08e 100755 --- a/setup.py +++ b/setup.py @@ -26,6 +26,8 @@ import subprocess from setuptools import find_packages, setup +os.chdir(os.path.split(os.path.abspath(__file__))[0]) + PKG = "hy" VERSIONFILE = os.path.join(PKG, "version.py") try: From ac01c0f03f915303ebbb8dd85a10dc3fb5296a64 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 13 Oct 2016 13:41:14 -0700 Subject: [PATCH 38/40] Changed version punctuation for PEP 440 compliance They now look like: 0.11.0+295.gec6e264.dirty instead of: 0.11.0-295-gec6e264-dirty --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4d0a08e..c84706c 100755 --- a/setup.py +++ b/setup.py @@ -31,8 +31,10 @@ os.chdir(os.path.split(os.path.abspath(__file__))[0]) PKG = "hy" VERSIONFILE = os.path.join(PKG, "version.py") try: - __version__ = subprocess.check_output([ - "git", "describe", "--tags", "--dirty"]).decode('ASCII').strip() + __version__ = (subprocess.check_output + (["git", "describe", "--tags", "--dirty"]) + .decode('ASCII').strip() + .replace('-', '+', 1).replace('-', '.')) with open(VERSIONFILE, "wt") as o: o.write("__version__ = {!r}\n".format(__version__)) except subprocess.CalledProcessError: From 14fddbe6c3e9a2859e18e0687897f533efd9c7c8 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 3 Nov 2016 00:35:58 -0700 Subject: [PATCH 39/40] Give `require` the same features as `import` (#1142) Give `require` the same features as `import` You can now do (require foo), (require [foo [a b c]]), (require [foo [*]]), and (require [foo :as bar]). The first and last forms get you macros named foo.a, foo.b, etc. or bar.a, bar.b, etc., respectively. The second form only gets the macros in the list. Implements #1118 and perhaps partly addresses #277. N.B. The new meaning of (require foo) will cause all existing code that uses macros to break. Simply replace these forms with (require [foo [*]]) to get your code working again. There's a bit of a hack involved in the forms (require foo) or (require [foo :as bar]). When you call (foo.a ...) or (bar.a ...), Hy doesn't actually look inside modules. Instead, these (require ...) forms give the macros names that have periods in them, which happens to work fine with the way Hy finds and interprets macro calls. * Make `require` syntax stricter and add tests * Update documentation for `require` * Documentation wording improvements * Allow :as in `require` name lists --- docs/contrib/anaphoric.rst | 2 +- docs/contrib/flow.rst | 4 +- docs/contrib/loop.rst | 2 +- docs/contrib/multi.rst | 2 +- docs/language/api.rst | 78 +++++++++++++++++++-- docs/tutorial.rst | 13 ++-- hy/cmdline.py | 4 +- hy/compiler.py | 43 ++++++++++-- hy/contrib/curry.hy | 3 +- hy/contrib/loop.hy | 6 +- hy/contrib/meth.hy | 12 ++-- hy/macros.py | 36 +++++++--- tests/compilers/test_ast.py | 14 ++++ tests/native_tests/contrib/alias.hy | 2 +- tests/native_tests/contrib/anaphoric.hy | 2 +- tests/native_tests/contrib/botsbuildbots.hy | 3 +- tests/native_tests/contrib/curry.hy | 2 +- tests/native_tests/contrib/loop.hy | 2 +- tests/native_tests/contrib/meth.hy | 2 +- tests/native_tests/contrib/multi.hy | 2 +- tests/native_tests/language.hy | 33 +++++++-- tests/resources/tlib.py | 9 ++- 22 files changed, 222 insertions(+), 54 deletions(-) diff --git a/docs/contrib/anaphoric.rst b/docs/contrib/anaphoric.rst index 51ece00..40341da 100644 --- a/docs/contrib/anaphoric.rst +++ b/docs/contrib/anaphoric.rst @@ -15,7 +15,7 @@ concise and easy to read. To use these macros you need to require the hy.contrib.anaphoric module like so: -``(require hy.contrib.anaphoric)`` +``(require [hy.contrib.anaphoric [*]])`` .. _ap-if: diff --git a/docs/contrib/flow.rst b/docs/contrib/flow.rst index fa15f73..e99e00e 100644 --- a/docs/contrib/flow.rst +++ b/docs/contrib/flow.rst @@ -25,7 +25,7 @@ Example: .. code-block:: hy - (require hy.contrib.flow) + (require [hy.contrib.flow [case]]) (defn temp-commenter [temp] (case temp @@ -48,7 +48,7 @@ Example: .. code-block:: hy - (require hy.contrib.flow) + (require [hy.contrib.flow [switch]]) (defn temp-commenter [temp] (switch temp diff --git a/docs/contrib/loop.rst b/docs/contrib/loop.rst index aaef239..e5a3766 100644 --- a/docs/contrib/loop.rst +++ b/docs/contrib/loop.rst @@ -45,7 +45,7 @@ Example: .. code-block:: hy - (require hy.contrib.loop) + (require [hy.contrib.loop [loop]]) (defn factorial [n] (loop [[i n] [acc 1]] diff --git a/docs/contrib/multi.rst b/docs/contrib/multi.rst index aa094cd..5740557 100644 --- a/docs/contrib/multi.rst +++ b/docs/contrib/multi.rst @@ -9,7 +9,7 @@ args and/or kwargs. Inspired by Clojure's take on ``defn``. .. code-block:: clj - => (require hy.contrib.multi) + => (require [hy.contrib.multi [defmulti]]) => (defmulti fun ... ([a] "a") ... ([a b] "a b") diff --git a/docs/language/api.rst b/docs/language/api.rst index fa09c82..379c6d4 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1210,16 +1210,84 @@ alternatively be written using the apostrophe (``'``) symbol. require ------- -``require`` is used to import macros from a given module. It takes at least one -parameter specifying the module which macros should be imported. Multiple -modules can be imported with a single ``require``. +``require`` is used to import macros from one or more given modules. It allows +parameters in all the same formats as ``import``. The ``require`` form itself +produces no code in the final program: its effect is purely at compile-time, for +the benefit of macro expansion. Specifically, ``require`` imports each named +module and then makes each requested macro available in the current module. -The following example will import macros from ``module-1`` and ``module-2``: +The following are all equivalent ways to call a macro named ``foo`` in the module ``mymodule``: .. code-block:: clj - (require module-1 module-2) + (require mymodule) + (mymodule.foo 1) + (require [mymodule :as M]) + (M.foo 1) + + (require [mymodule [foo]]) + (foo 1) + + (require [mymodule [*]]) + (foo 1) + + (require [mymodule [foo :as bar]]) + (bar 1) + +Macros that call macros +~~~~~~~~~~~~~~~~~~~~~~~ + +One aspect of ``require`` that may be surprising is what happens when one +macro's expansion calls another macro. Suppose ``mymodule.hy`` looks like this: + +.. code-block:: clj + + (defmacro repexpr [n expr] + ; Evaluate the expression n times + ; and collect the results in a list. + `(list (map (fn [_] ~expr) (range ~n)))) + + (defmacro foo [n] + `(repexpr ~n (input "Gimme some input: "))) + +And then, in your main program, you write: + +.. code-block:: clj + + (require [mymodule [foo]]) + + (print (mymodule.foo 3)) + +Running this raises ``NameError: name 'repexpr' is not defined``, even though +writing ``(print (foo 3))`` in ``mymodule`` works fine. The trouble is that your +main program doesn't have the macro ``repexpr`` available, since it wasn't +imported (and imported under exactly that name, as opposed to a qualified name). +You could do ``(require [mymodule [*]])`` or ``(require [mymodule [foo +repexpr]])``, but a less error-prone approach is to change the definition of +``foo`` to require whatever sub-macros it needs: + +.. code-block:: clj + + (defmacro foo [n] + `(do + (require mymodule) + (mymodule.repexpr ~n (raw-input "Gimme some input: ")))) + +It's wise to use ``(require mymodule)`` here rather than ``(require [mymodule +[repexpr]])`` to avoid accidentally shadowing a function named ``repexpr`` in +the main program. + +Qualified macro names +~~~~~~~~~~~~~~~~~~~~~ + +Note that in the current implementation, there's a trick in qualified macro +names, like ``mymodule.foo`` and ``M.foo`` in the above example. These names +aren't actually attributes of module objects; they're just identifiers with +periods in them. In fact, ``mymodule`` and ``M`` aren't defined by these +``require`` forms, even at compile-time. None of this will hurt you unless try +to do introspection of the current module's set of defined macros, which isn't +really supported anyway. rest / cdr ---------- diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 834332d..5c3225c 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -568,15 +568,18 @@ Macros are useful when one wishes to extend Hy or write their own language on top of that. Many features of Hy are macros, like ``when``, ``cond`` and ``->``. -To use macros defined in a different module, it is not enough to -``import`` the module, because importing happens at run-time, while we -would need macros at compile-time. Instead of importing the module -with macros, ``require`` must be used: +What if you want to use a macro that's defined in a different +module? The special form ``import`` won't help, because it merely +translates to a Python ``import`` statement that's executed at +run-time, and macros are expanded at compile-time, that is, +during the translate from Hy to Python. Instead, use ``require``, +which imports the module and makes macros available at +compile-time. ``require`` uses the same syntax as ``import``. .. code-block:: clj => (require tutorial.macros) - => (rev (1 2 3 +)) + => (tutorial.macros.rev (1 2 3 +)) 6 Hy <-> Python interop diff --git a/hy/cmdline.py b/hy/cmdline.py index 35f75c6..c24a2f8 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -173,8 +173,8 @@ def ideas_macro(): """)]) -require("hy.cmdline", "__console__") -require("hy.cmdline", "__main__") +require("hy.cmdline", "__console__", all_macros=True) +require("hy.cmdline", "__main__", all_macros=True) SIMPLE_TRACEBACKS = True diff --git a/hy/compiler.py b/hy/compiler.py index b6c6d27..aa993b7 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1737,10 +1737,45 @@ class HyASTCompiler(object): "unimport" it after we've completed `thing' so that we don't pollute other envs. """ - expression.pop(0) - for entry in expression: - __import__(entry) # Import it fo' them macros. - require(entry, self.module_name) + for entry in expression[1:]: + if isinstance(entry, HySymbol): + # e.g., (require foo) + __import__(entry) + require(entry, self.module_name, all_macros=True, + prefix=entry) + elif isinstance(entry, HyList) and len(entry) == 2: + # e.g., (require [foo [bar baz :as MyBaz bing]]) + # or (require [foo [*]]) + module, names = entry + if not isinstance(names, HyList): + raise HyTypeError(names, + "(require) name lists should be HyLists") + __import__(module) + if '*' in names: + if len(names) != 1: + raise HyTypeError(names, "* in a (require) name list " + "must be on its own") + require(module, self.module_name, all_macros=True) + else: + assignments = {} + while names: + if len(names) > 1 and names[1] == HyKeyword(":as"): + k, _, v = names[:3] + del names[:3] + assignments[k] = v + else: + symbol = names.pop(0) + assignments[symbol] = symbol + require(module, self.module_name, assignments=assignments) + elif (isinstance(entry, HyList) and len(entry) == 3 + and entry[1] == HyKeyword(":as")): + # e.g., (require [foo :as bar]) + module, _, prefix = entry + __import__(module) + require(module, self.module_name, all_macros=True, + prefix=prefix) + else: + raise HyTypeError(entry, "unrecognized (require) syntax") return Result() @builds("and") diff --git a/hy/contrib/curry.hy b/hy/contrib/curry.hy index ae3e4da..0bf5bd7 100644 --- a/hy/contrib/curry.hy +++ b/hy/contrib/curry.hy @@ -17,4 +17,5 @@ (defmacro defnc [name args &rest body] - `(def ~name (fnc [~@args] ~@body))) + `(do (require hy.contrib.curry) + (def ~name (hy.contrib.curry.fnc [~@args] ~@body)))) diff --git a/hy/contrib/loop.hy b/hy/contrib/loop.hy index a9b98f6..d114140 100644 --- a/hy/contrib/loop.hy +++ b/hy/contrib/loop.hy @@ -70,7 +70,8 @@ (defmacro defnr [name lambda-list &rest body] (if (not (= (type name) HySymbol)) (macro-error name "defnr takes a name as first argument")) - `(setv ~name (fnr ~lambda-list ~@body))) + `(do (require hy.contrib.loop) + (setv ~name (hy.contrib.loop.fnr ~lambda-list ~@body)))) (defmacro/g! loop [bindings &rest body] @@ -87,5 +88,6 @@ ;; and erroring if not is a giant TODO. (let [fnargs (map (fn [x] (first x)) bindings) initargs (map second bindings)] - `(do (defnr ~g!recur-fn [~@fnargs] ~@body) + `(do (require hy.contrib.loop) + (hy.contrib.loop.defnr ~g!recur-fn [~@fnargs] ~@body) (~g!recur-fn ~@initargs)))) diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy index c52e03c..6774830 100644 --- a/hy/contrib/meth.hy +++ b/hy/contrib/meth.hy @@ -9,19 +9,23 @@ (defn ~name ~params (do ~@code))))) +(defn rwm [name path method params code] + `(do (require hy.contrib.meth) + (hy.contrib.meth.route-with-methods ~name ~path ~method ~params ~@code))) + ;; Some macro examples (defmacro route [name path params &rest code] "Get request" - `(route-with-methods ~name ~path ["GET"] ~params ~@code)) + (rwm name path ["GET"] params code)) (defmacro post-route [name path params &rest code] "Post request" - `(route-with-methods ~name ~path ["POST"] ~params ~@code)) + (rwm name path ["POST"] params code)) (defmacro put-route [name path params &rest code] "Put request" - `(route-with-methods ~name ~path ["PUT"] ~params ~@code)) + (rwm name path ["PUT"] params code)) (defmacro delete-route [name path params &rest code] "Delete request" - `(route-with-methods ~name ~path ["DELETE"] ~params ~@code)) + (rwm name path ["DELETE"] params code)) diff --git a/hy/macros.py b/hy/macros.py index 154e258..e59f760 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -84,22 +84,36 @@ def reader(name): return _ -def require(source_module, target_module): - """Load the macros from `source_module` in the namespace of - `target_module`. +def require(source_module, target_module, + all_macros=False, assignments={}, prefix=""): + """Load macros from `source_module` in the namespace of + `target_module`. `assignments` maps old names to new names, but is + ignored if `all_macros` is true. If `prefix` is nonempty, it is + prepended to the name of each imported macro. (This means you get + macros named things like "mymacromodule.mymacro", which looks like + an attribute of a module, although it's actually just a symbol + with a period in its name.) This function is called from the `require` special form in the compiler. """ - macros = _hy_macros[source_module] - refs = _hy_macros[target_module] - for name, macro in macros.items(): - refs[name] = macro - readers = _hy_reader[source_module] - reader_refs = _hy_reader[target_module] - for name, reader in readers.items(): - reader_refs[name] = reader + seen_names = set() + if prefix: + prefix += "." + + for d in _hy_macros, _hy_reader: + for name, macro in d[source_module].items(): + seen_names.add(name) + if all_macros: + d[target_module][prefix + name] = macro + elif name in assignments: + d[target_module][prefix + assignments[name]] = macro + + if not all_macros: + unseen = frozenset(assignments.keys()).difference(seen_names) + if unseen: + raise ImportError("cannot require names: " + repr(list(unseen))) def load_macros(module_name): diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 31c90bd..67e645e 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -249,6 +249,20 @@ def test_ast_good_import_from(): can_compile("(import [x [y]])") +def test_ast_require(): + "Make sure AST respects (require) syntax" + can_compile("(require tests.resources.tlib)") + can_compile("(require [tests.resources.tlib [qplah parald]])") + can_compile("(require [tests.resources.tlib [*]])") + can_compile("(require [tests.resources.tlib :as foobar])") + can_compile("(require [tests.resources.tlib [qplah :as quiz]])") + can_compile("(require [tests.resources.tlib [qplah :as quiz parald]])") + cant_compile("(require [tests.resources.tlib])") + cant_compile("(require [tests.resources.tlib [* qplah]])") + cant_compile("(require [tests.resources.tlib [qplah *]])") + cant_compile("(require [tests.resources.tlib [* *]])") + + def test_ast_good_get(): "Make sure AST can compile valid get" can_compile("(get x y)") diff --git a/tests/native_tests/contrib/alias.hy b/tests/native_tests/contrib/alias.hy index 7f3e66c..a88cb93 100644 --- a/tests/native_tests/contrib/alias.hy +++ b/tests/native_tests/contrib/alias.hy @@ -1,4 +1,4 @@ -(require hy.contrib.alias) +(require [hy.contrib.alias [defn-alias]]) (defn test-defn-alias [] (defn-alias [tda-main tda-a1 tda-a2] [] :bazinga) diff --git a/tests/native_tests/contrib/anaphoric.hy b/tests/native_tests/contrib/anaphoric.hy index 69caac2..60f2998 100644 --- a/tests/native_tests/contrib/anaphoric.hy +++ b/tests/native_tests/contrib/anaphoric.hy @@ -19,7 +19,7 @@ ;; DEALINGS IN THE SOFTWARE. (import [hy.errors [HyMacroExpansionError]]) -(require hy.contrib.anaphoric) +(require [hy.contrib.anaphoric [*]]) ;;;; some simple helpers diff --git a/tests/native_tests/contrib/botsbuildbots.hy b/tests/native_tests/contrib/botsbuildbots.hy index 7fc0d7b..1355119 100644 --- a/tests/native_tests/contrib/botsbuildbots.hy +++ b/tests/native_tests/contrib/botsbuildbots.hy @@ -1,5 +1,4 @@ -(import [hy.contrib.botsbuildbots [*]]) -(require hy.contrib.botsbuildbots) +(require [hy.contrib.botsbuildbots [Botsbuildbots]]) (defn test-botsbuildbots [] (assert (> (len (first (Botsbuildbots))) 50))) diff --git a/tests/native_tests/contrib/curry.hy b/tests/native_tests/contrib/curry.hy index 5d73ca7..7db504f 100644 --- a/tests/native_tests/contrib/curry.hy +++ b/tests/native_tests/contrib/curry.hy @@ -1,4 +1,4 @@ -(require hy.contrib.curry) +(require [hy.contrib.curry [defnc]]) (defnc s [x y z] ((x z) (y z))) ; λxyz.xz(yz) diff --git a/tests/native_tests/contrib/loop.hy b/tests/native_tests/contrib/loop.hy index 175111a..5b67c5c 100644 --- a/tests/native_tests/contrib/loop.hy +++ b/tests/native_tests/contrib/loop.hy @@ -1,4 +1,4 @@ -(require hy.contrib.loop) +(require [hy.contrib.loop [loop]]) (import sys) (defn tco-sum [x y] diff --git a/tests/native_tests/contrib/meth.hy b/tests/native_tests/contrib/meth.hy index e1d42da..7fe30f9 100644 --- a/tests/native_tests/contrib/meth.hy +++ b/tests/native_tests/contrib/meth.hy @@ -1,4 +1,4 @@ -(require hy.contrib.meth) +(require [hy.contrib.meth [route post-route put-route delete-route]]) (defclass FakeMeth [] "Mocking decorator class" diff --git a/tests/native_tests/contrib/multi.hy b/tests/native_tests/contrib/multi.hy index 5ce9932..51162b0 100644 --- a/tests/native_tests/contrib/multi.hy +++ b/tests/native_tests/contrib/multi.hy @@ -18,7 +18,7 @@ ;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ;; DEALINGS IN THE SOFTWARE. -(require hy.contrib.multi) +(require [hy.contrib.multi [defmulti]]) (defn test-basic-multi [] diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 54b3e61..b9385db 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -1101,11 +1101,34 @@ (defn test-require [] "NATIVE: test requiring macros from python code" - (try - (assert (= "this won't happen" (qplah 1 2 3 4))) - (except [NameError])) + (try (qplah 1 2 3 4) + (except [NameError] True) + (else (assert False))) + (try (parald 1 2 3 4) + (except [NameError] True) + (else (assert False))) + (require [tests.resources.tlib [qplah]]) + (assert (= (qplah 1 2 3) [8 1 2 3])) + (try (parald 1 2 3 4) + (except [NameError] True) + (else (assert False))) (require tests.resources.tlib) - (assert (= [1 2 3] (qplah 1 2 3)))) + (assert (= (tests.resources.tlib.parald 1 2 3) [9 1 2 3])) + (try (parald 1 2 3 4) + (except [NameError] True) + (else (assert False))) + (require [tests.resources.tlib :as T]) + (assert (= (T.parald 1 2 3) [9 1 2 3])) + (try (parald 1 2 3 4) + (except [NameError] True) + (else (assert False))) + (require [tests.resources.tlib [parald :as p]]) + (assert (= (p 1 2 3) [9 1 2 3])) + (try (parald 1 2 3 4) + (except [NameError] True) + (else (assert False))) + (require [tests.resources.tlib [*]]) + (assert (= (parald 1 2 3) [9 1 2 3]))) (defn test-require-native [] @@ -1125,7 +1148,7 @@ (assert (= x [3 2 1])) "success") (except [NameError] "failure")))) - (require tests.native_tests.native_macros) + (require [tests.native_tests.native_macros [rev]]) (assert (= "success" (try (do (setv x []) diff --git a/tests/resources/tlib.py b/tests/resources/tlib.py index 7ce0ee5..bc214a0 100644 --- a/tests/resources/tlib.py +++ b/tests/resources/tlib.py @@ -1,7 +1,12 @@ from hy.macros import macro -from hy import HyList +from hy import HyList, HyInteger @macro("qplah") def tmac(*tree): - return HyList(tree) + return HyList((HyInteger(8), ) + tree) + + +@macro("parald") +def tmac2(*tree): + return HyList((HyInteger(9), ) + tree) From f60ed24c299b179cff90be34c484ef37a90b3408 Mon Sep 17 00:00:00 2001 From: Tuukka Turto Date: Thu, 3 Nov 2016 10:20:26 +0200 Subject: [PATCH 40/40] Add docs and tests for as-> macro (#1141) Add docs and tests for as-> macro Closes #1047 --- docs/language/api.rst | 73 ++++++++++++++++++++++++++++++++++ hy/core/macros.hy | 2 +- tests/native_tests/language.hy | 23 +++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 379c6d4..835d26e 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -179,6 +179,79 @@ other case, the first false value will be returned. Example usage: False +as-> +---- + +.. versionadded:: 0.12.0 + +Expands to sequence of assignments to the provided name, starting with head. +The previous result is thus available in the subsequent form. Returns the final +result, and leaves the name bound to it in the local scope. This behaves much +like the other threading macros, but requires you to specify the threading +point per form via the name instead of always the first or last argument. + +.. code-block:: clj + + ;; example how -> and as-> relate + + => (as-> 0 it + ... (inc it) + ... (inc it)) + 2 + + => (-> 0 inc inc) + 2 + + ;; create data for our cuttlefish database + + => (setv data [{:name "hooded cuttlefish" + ... :classification {:subgenus "Acanthosepion" + ... :species "Sepia prashadi"} + ... :discovered {:year 1936 + ... :name "Ronald Winckworth"}} + ... {:name "slender cuttlefish" + ... :classification {:subgenus "Doratosepion" + ... :species "Sepia braggi"} + ... :discovered {:year 1907 + ... :name "Sir Joseph Cooke Verco"}}]) + + ;; retrieve name of first entry + => (as-> (first data) it + ... (:name it)) + 'hooded cuttlefish' + + ;; retrieve species of first entry + => (as-> (first data) it + ... (:classification it) + ... (:species it)) + 'Sepia prashadi' + + ;; find out who discovered slender cuttlefish + => (as-> (filter (fn [entry] (= (:name entry) + ... "slender cuttlefish")) data) it + ... (first it) + ... (:discovered it) + ... (:name it)) + 'Sir Joseph Cooke Verco' + + ;; more convoluted example to load web page and retrieve data from it + => (import [urllib.request [urlopen]]) + => (as-> (urlopen "http://docs.hylang.org/en/stable/") it + ... (.read it) + ... (.decode it "utf-8") + ... (drop (.index it "Welcome") it) + ... (take 30 it) + ... (list it) + ... (.join "" it)) + 'Welcome to Hy’s documentation! + +.. note:: + + In these examples, the REPL will report a tuple (e.g. `('Sepia prashadi', + 'Sepia prashadi')`) as the result, but only a single value is actually + returned. + + assert ------ diff --git a/hy/core/macros.hy b/hy/core/macros.hy index e6c8e16..606f722 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -35,7 +35,7 @@ The previous result is thus available in the subsequent form. Returns the final result, and leaves the name bound to it in the local scope. This behaves much like the other threading macros, but requires you to specify the threading - point per form via the name instead of always the first or last arument." + point per form via the name instead of always the first or last argument." `(do (setv ~name ~head ~@(interleave (repeat name) rest)) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index b9385db..b06b714 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -588,6 +588,29 @@ ["X" "B" "C" "D"]))) +(defn test-as-threading [] + "NATIVE: test as threading macro" + (setv data [{:name "hooded cuttlefish" + :classification {:subgenus "Acanthosepion" + :species "Sepia prashadi"} + :discovered {:year 1936 + :name "Ronald Winckworth"}} + {:name "slender cuttlefish" + :classification {:subgenus "Doratosepion" + :species "Sepia braggi"} + :discovered {:year 1907 + :name "Sir Joseph Cooke Verco"}}]) + (assert (= (as-> (first data) x + (:name x)) + "hooded cuttlefish")) + (assert (= (as-> (filter (fn [entry] (= (:name entry) + "slender cuttlefish")) data) x + (first x) + (:discovered x) + (:name x)) + "Sir Joseph Cooke Verco"))) + + (defn test-assoc [] "NATIVE: test assoc" (setv vals {"one" "two"})