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/.travis.yml b/.travis.yml index 9f8dea7..bd99240 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ matrix: include: - python: 3.5 env: TOXENV=py35 + - python: 3.6-dev + env: TOXENV=py36 env: - TOXENV=py27 - TOXENV=py33 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 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 01ba87a..0e01d57 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. @@ -36,7 +38,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 @@ -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 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 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/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/). diff --git a/docs/contrib/anaphoric.rst b/docs/contrib/anaphoric.rst index cce39e0..40341da 100644 --- a/docs/contrib/anaphoric.rst +++ b/docs/contrib/anaphoric.rst @@ -11,11 +11,11 @@ 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: -``(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/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/loop.rst b/docs/contrib/loop.rst index 9968aab..e5a3766 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 ====== @@ -39,13 +39,13 @@ 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: .. 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 4071406..5a2d622 100644 --- a/docs/contrib/multi.rst +++ b/docs/contrib/multi.rst @@ -2,7 +2,7 @@ defmulti ======== -.. versionadded:: 0.10.0 +.. versionadded:: 0.11.0 ``defmulti``, ``defmethod`` and ``default-method`` lets you define multimethods where a dispatching function is used to select between different @@ -11,7 +11,7 @@ on the code by `Adam Bard`_. .. code-block:: clj - => (require hy.contrib.multi) + => (require [hy.contrib.multi [defmulti defmethod]]) => (defmulti area [shape] ... "calculate area of a shape" ... (:type shape)) @@ -59,7 +59,7 @@ different implementations can narrow them down. .. code-block:: clj - => (require hy.contrib.multi) + => (require [hy.contrib.multi [defmulti defmethod]]) => (defmulti fun [&rest args] ... (len args)) 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]]]) diff --git a/docs/hacking.rst b/docs/hacking.rst index b902a68..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 @@ -63,7 +63,7 @@ Test! ===== Tests are located in ``tests/``. We use `nose -`_. +`_. To run the tests:: diff --git a/docs/language/api.rst b/docs/language/api.rst index c96e0b6..835d26e 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``. @@ -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 ------ @@ -953,11 +1026,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 [*]]) @@ -1201,16 +1283,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/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 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? ------ 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 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:: 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)) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f455bd3..5c3225c 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 @@ -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 @@ -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/__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/cmdline.py b/hy/cmdline.py index 195a5bc..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 @@ -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") diff --git a/hy/compiler.py b/hy/compiler.py index c2e4dc3..aa993b7 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, @@ -1516,7 +1510,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) @@ -1742,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") @@ -1910,7 +1940,6 @@ class HyASTCompiler(object): col_offset=child.start_column) return ret - @builds("+") @builds("*") @builds("/") @builds("//") @@ -1918,8 +1947,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 +1958,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("//=") @@ -2449,6 +2491,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", "&key"): + 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/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/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/hy/core/macros.hy b/hy/core/macros.hy index d39aabe..606f722 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -30,6 +30,16 @@ [hy.models.symbol [HySymbol]] [hy._compat [PY33 PY34]]) +(defmacro as-> [head name &rest rest] + "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." + `(do (setv + ~name ~head + ~@(interleave (repeat name) rest)) + ~name)) (defmacro with [args &rest body] "shorthand for nested with* loops: 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/hy/importer.py b/hy/importer.py index 8fcbd27..8418b7c 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()) @@ -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) 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/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/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..c84706c 100755 --- a/setup.py +++ b/setup.py @@ -22,24 +22,23 @@ import os import re import sys +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") -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() + .replace('-', '+', 1).replace('-', '.')) + 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 diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 4898c40..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)") @@ -397,6 +411,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 +425,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(): 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/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 951c896..0a33fc8 100644 --- a/tests/native_tests/contrib/multi.hy +++ b/tests/native_tests/contrib/multi.hy @@ -19,7 +19,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-different-signatures [] diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 9e3ad52..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] @@ -33,6 +35,12 @@ (defn assert-nil [x] (assert (is x nil))) +(defn assert-requires-num [f] + (for [x ["foo" [] None]] + (try (f x) + (except [TypeError] True) + (else (assert False))))) + (defn test-coll? [] "NATIVE: testing coll?" (assert-true (coll? [1 2 3])) @@ -66,12 +74,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 +176,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 +261,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 +391,14 @@ (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)))))) + (when PY3 + (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 +450,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 +482,8 @@ (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)))))) + (when PY3 + (assert-requires-num pos?))) (defn test-remove [] "NATIVE: testing the remove function" diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 0300ffb..b06b714 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)))) @@ -587,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"}) @@ -1100,11 +1124,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 [] @@ -1124,7 +1171,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/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/native_macros.hy b/tests/native_tests/native_macros.hy index f833a30..788e52b 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)))))) @@ -37,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)) @@ -48,6 +53,26 @@ (defmacro bar [x y] (foo x y)) +(defn test-macro-kw [] + "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] + (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))) + + (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 [] "NATIVE: test macro calling a plain function" (assert (= 3 (bar 1 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"])))) 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) 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]