From 3a9cf486db3673ab7f5bbdecee5048592e66871c Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 1 Apr 2014 23:23:53 -0400 Subject: [PATCH 01/15] Add a keyword? function to detect keywords. Thanks to algernon for the function body; huge improvement. --- hy/core/language.hy | 7 ++++++- tests/native_tests/core.hy | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index cdef29e..0df4b44 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -44,6 +44,11 @@ "Check whether c can be used as a cons object" (instance? HyCons c)) +(defn keyword? [k] + "Check whether k is a keyword" + (and (instance? (type :foo) k) + (.startswith k (get :foo 0)))) + (defn cycle [coll] "Yield an infinite repetition of the items in coll" (setv seen []) @@ -354,7 +359,7 @@ (def *exports* '[calling-module-name coll? cons cons? cycle dec distinct disassemble drop drop-while empty? even? every? first filter flatten float? gensym identity inc instance? integer - integer? integer-char? iterable? iterate iterator? + integer? integer-char? iterable? iterate iterator? keyword? list* macroexpand macroexpand-1 neg? nil? none? nth numeric? odd? pos? remove repeat repeatedly rest second some string string? take take-nth take-while zero? zipwith]) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 15debf4..98e403d 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -478,3 +478,14 @@ (assert-equal (list res) [4 4 4]) (setv res (zipwith operator.sub [3 7 9] [1 2 4])) (assert-equal (list res) [2 5 5])) + +(defn test-is-keyword [] + "NATIVE: testing the keyword? function" + (assert (keyword? ':bar)) + (assert (keyword? ':baz)) + (assert (keyword? :bar)) + (assert (keyword? :baz)) + (assert (not (keyword? "foo"))) + (assert (not (keyword? ":foo"))) + (assert (not (keyword? 1))) + (assert (not (keyword? nil)))) From f3d978c5a3b16fe9e9915fd5311f2d2b4edd0f2b Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Mon, 24 Feb 2014 09:39:45 -0600 Subject: [PATCH 02/15] Added lisp-if (or lif), with tests and documentation. Now everyone can get along and everyone can have a pony, and that pony is actually a unicorn that shoots rainbows from its horn. --- docs/language/api.rst | 28 ++++++++++++++++++++++++++++ hy/core/macros.hy | 5 +++++ tests/native_tests/native_macros.hy | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index 57f6029..ef9099a 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -726,6 +726,34 @@ any numeric type, empty sequence and empty dictionary are considered `False`. Everything else is considered `True`. +lisp-if / lif +------------- + +For those that prefer a more lisp-y if clause, we have lisp-if, or lif. This +*only* considers None/nil as false! All other values of python +"falseiness" are considered true. + + +.. code-block:: clj + + => (lisp-if True "true" "false") + "true" + => (lisp-if False "true" "false") + "true" + => (lisp-if 0 "true" "false") + "true" + => (lisp-if nil "true" "false") + "false" + => (lisp-if None "true" "false") + "false" + + ; And, same thing + => (lif True "true" "false") + "true" + => (lif nil "true" "false") + "false" + + import ------ diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 531350e..486cf0e 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -145,6 +145,11 @@ `(if (not ~test) ~not-branch ~yes-branch))) +(defmacro-alias [lisp-if lif] [test &rest branches] + "Like `if`, but anything that is not None/nil is considered true." + `(if (is-not ~test nil) ~@branches)) + + (defmacro when [test &rest body] "Execute `body` when `test` is true" `(if ~test (do ~@body))) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 5bdf821..654c06d 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -191,6 +191,25 @@ :yes))) +(defn test-lisp-if [] + "test that lisp-if works as expected" + ; nil is false + (assert (= (lisp-if None "true" "false") "false")) + (assert (= (lisp-if nil "true" "false") "false")) + + ; But everything else is True! Even falsey things. + (assert (= (lisp-if True "true" "false") "true")) + (assert (= (lisp-if False "true" "false") "true")) + (assert (= (lisp-if 0 "true" "false") "true")) + (assert (= (lisp-if "some-string" "true" "false") "true")) + (assert (= (lisp-if "" "true" "false") "true")) + (assert (= (lisp-if (+ 1 2 3) "true" "false") "true")) + + ; Just to be sure, test the alias lif + (assert (= (lif nil "true" "false") "false")) + (assert (= (lif 0 "true" "false") "true"))) + + (defn test-defn-alias [] (defn-alias [tda-main tda-a1 tda-a2] [] :bazinga) (defun-alias [tda-main tda-a1 tda-a2] [] :bazinga) From d5194f23fa347c31118f5ea462945fd3ac083694 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 23 Feb 2014 10:48:33 -0600 Subject: [PATCH 03/15] Properly add yield-from, using python's real "yield from". The yield-from that existed previously wasn't actually implementing the full complexity of "yield from": http://legacy.python.org/dev/peps/pep-0380/#formal-semantics ... this includes passing along errors, and many other things. Also removes the yield-from backport macro, since it does not seem possible at present to conditionally build macros. Thus, there is no longer yield-from on pre-python-3.3 systems. Includes updated docs and tests to reflect all this. --- docs/language/api.rst | 14 ++++++++++++++ hy/compiler.py | 22 ++++++++++++++++++++++ hy/core/macros.hy | 10 ++-------- tests/native_tests/native_macros.hy | 20 +++++++++++++++----- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 57f6029..2250760 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1227,6 +1227,20 @@ infinite series without consuming infinite amount of memory. => (list-comp x [x (take 15 (random-numbers 1 50))])]) [7, 41, 6, 22, 32, 17, 5, 38, 18, 38, 17, 14, 23, 23, 19] + +yield-from +---------- + +.. versionadded:: 0.9.13 + +**PYTHON 3.3 AND UP ONLY!** + +`yield-from` is used to call a subgenerator. This is useful if you +want your coroutine to be able to delegate its processes to another +coroutine, say if using something fancy like +`asyncio `_. + + .. _zipwith: zipwith diff --git a/hy/compiler.py b/hy/compiler.py index dbb4782..2122ea9 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1017,6 +1017,28 @@ class HyASTCompiler(object): return ret + @builds("yield_from") + @checkargs(max=1) + def compile_yield_from_expression(self, expr): + if not PY33: + raise HyCompileError( + "yield-from only supported in python 3.3+!") + + expr.pop(0) + ret = Result(contains_yield=True) + + value = None + if expr != []: + ret += self.compile(expr.pop(0)) + value = ret.force_expr + + ret += ast.YieldFrom( + value=value, + lineno=expr.start_line, + col_offset=expr.start_column) + + return ret + @builds("import") def compile_import_expression(self, expr): def _compile_import(expr, module, names=None, importer=ast.Import): diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 531350e..69567cf 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -27,8 +27,8 @@ (import [hy.models.list [HyList]] - [hy.models.symbol [HySymbol]]) - + [hy.models.symbol [HySymbol]] + [hy._compat [PY33 PY34]]) (defmacro for [args &rest body] @@ -155,12 +155,6 @@ `(if-not ~test (do ~@body))) -(defmacro yield-from [iterable] - "Yield all the items from iterable" - (let [[x (gensym)]] - `(for* [~x ~iterable] - (yield ~x)))) - (defmacro with-gensyms [args &rest body] `(let ~(HyList (map (fn [x] `[~x (gensym '~x)]) args)) ~@body)) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 5bdf821..18dd588 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -1,3 +1,6 @@ +(import [hy._compat [PY33]]) +(import [hy.errors [HyCompileError]]) + (defmacro rev [&rest body] "Execute the `body` statements in reverse" (quasiquote (do (unquote-splice (list (reversed body)))))) @@ -99,11 +102,18 @@ (defn test-yield-from [] "NATIVE: testing yield from" - (defn yield-from-test [] - (for* [i (range 3)] - (yield i)) - (yield-from [1 2 3])) - (assert (= (list (yield-from-test)) [0 1 2 1 2 3]))) + + (try + (eval + '(do (defn yield-from-test [] + (for* [i (range 3)] + (yield i)) + (yield-from [1 2 3])) + (assert (= (list (yield-from-test)) [0 1 2 1 2 3])))) + (catch [e HyCompileError] + ;; Yup, this should happen on non-Python3.3 thingies + (assert (not PY33))) + (else (assert PY33)))) (defn test-if-python2 [] (import sys) From 10149f66ecbd217790d34638a8b63ea8ef0d14bb Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Thu, 10 Apr 2014 23:00:53 -0400 Subject: [PATCH 04/15] NEWS! --- NEWS | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/NEWS b/NEWS index 3670fca..0c01556 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,83 @@ +Changes from 0.9.12 + + 0.10.0 - the "oh man I'm late for PyCon" release + + Thanks to theanalyst (Abhi) for getting the release notes + together. You're the best! + - Hy Society + + [ Breaking Changes ] + + We're calling this release 0.10 because we broke + API. Sorry about that. We've removed kwapply in + favor of using `apply`. Please be sure to upgrade + all code to work with `apply`. + + (apply function-call args kwargs) ; is the signature + + [Thanks] + + Major shoutout to Clinton Dreisbach for implementing loop/recur. + As always, massive hugs to olasd for the constant reviews and for + implementing HyCons cells. Thanks to @kenanb for redesigning the + new Hy logo. + + Many thanks to algernon for working on adderall, which helped + push Hy further this cycle. Adderall is an implementation of miniKanren + in Hy. If you're interested in using Adderall, check out hydiomatic, + which prettfies Hy source using Adderall rules. + + This release saw an increase of about 11 contributors for a point + release, you guys rock! + + -Hy Society + + [ Language Changes ] + + * `for' revamped again (Last time, we hope!), this time using a saner + itertools.product when nesting + * `lisp-if'/`lif' added for the lisp-like everything is true if, giving + seasoned lispers a better if check (0 is a value, etc) + * Reader Macros are macros now! + * yield-from is now a proper yield from on Python 3. It also now breaks on + Python 2.x. + * Added if-not macro + * We finally have a lisp like cons cells + * Generator expressions, set & dict comprehensions are now supported + * (.) is a mini DSL for attribute access + * `macroexpand' & `macroexpand-1' added to core + * `disassemble' added to core, which dumps the AST or equivalent python code + * `coll?' added to core to check for a collection + * `identity' function added to core + + [ Misc. Fixes ] + * Lots of doc fixes. Reorganization as well as better docs on Hy internals + * Universal Wheel Support + * Pygments > 1.6 supports Hy now. All codeblocks in docs have been changed + from clojure to hy + * Hy REPL supports invoking with --spy & -i options [reword] + * `first' and `rest' are functions and not macros anymore + * "clean" target added to Makefile + * hy2py supports a bunch of commandline options to show AST, source etc. + * Sub-object mangling: every identifier is split along the dots & mangled + seperately + + [ Bug Fixes ] + * Empty MacroExpansions work as expected + * Python 3.4 port. Sorry this wasn't in a 3.4 release time, we forgot to do + a release. Whoops. + * eg/lxml/parse-tumblr.hy works with Python 3 + * hy2py works on Windows + * Fixed unicode encoding issue in REPL during unicode exceptions + * Fixed handling of comments at end of input (#382) + + [ Contrib changes ] + * Curry module added to contrib + * Loop/recur module added which provides TCO at tail position + * defmulti has been added - check out more in the docs -- thanks to Foxboron for this one! + * Walk module for walking the Hy AST, features a `macroexpand-all` as well + + Changes from Hy 0.9.11 tl;dr: From 6072557b6c10cd9a21145781e26ad1f978ed14b9 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Thu, 10 Apr 2014 23:01:11 -0400 Subject: [PATCH 05/15] 0.10: The "Oh fuck it's PyCon" release --- hy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/version.py b/hy/version.py index 2c33563..23a31bc 100644 --- a/hy/version.py +++ b/hy/version.py @@ -20,4 +20,4 @@ __appname__ = "hy" -__version__ = "0.9.12" +__version__ = "0.10.0" From 07b9371cb82e3c16cd633713add8faaf3d87512a Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sun, 13 Apr 2014 17:42:48 +0300 Subject: [PATCH 06/15] Add a missing versionadded directive to the lisp-if/lif documentation. --- docs/language/api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index 430be99..a5e86f2 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -729,6 +729,8 @@ Everything else is considered `True`. lisp-if / lif ------------- +.. versionadded:: 0.10.0 + For those that prefer a more lisp-y if clause, we have lisp-if, or lif. This *only* considers None/nil as false! All other values of python "falseiness" are considered true. From c5dfba3c4ffe7edd1559bdbe25b3771f43115576 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sun, 13 Apr 2014 18:04:42 +0300 Subject: [PATCH 07/15] Fix a code-block in the lambda documentation. --- docs/language/api.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index 430be99..be07a3e 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -814,11 +814,15 @@ Just as in normal function definitions, if the first element of the body is a string, it serves as docstring. This is useful for giving class methods docstrings. +.. code-block:: clj + => (setv times-three ... (fn [x] ... "Multiplies input by three and returns the result." ... (* x 3))) +Then test it via the Python built-in ``help`` function:: + => (help times-three) Help on function times_three: From a76c3d1f5107bf05cfc0fd9c27f37613b33ffa8c Mon Sep 17 00:00:00 2001 From: Allison Kaptur Date: Wed, 16 Apr 2014 13:40:21 -0400 Subject: [PATCH 08/15] add make flow to the hacking docs --- docs/hacking.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/hacking.rst b/docs/hacking.rst index 4329c68..8b9ecc6 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -92,6 +92,12 @@ Write docs---docs are good! Even this doc! Core Development Rules ====================== +Pull requests are good! + +Before you submit a PR, please run the tests and check your code against the style guide. You can do both these things at once:: + + $ make d + All incoming changes need to be acked by 2 different members of Hylang's core team. Additional review is clearly welcome, but we need a minimum of 2 signoffs for any change. From e57047289ace429026f63b8c5f74c3c66a8a33bd Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 19 Apr 2014 23:29:44 +0300 Subject: [PATCH 09/15] Cleanup requirements, .travis.yml and tox.ini. --- .travis.yml | 12 +++++------- requirements-dev.txt | 11 ++++++++--- requirements.txt | 3 --- tox.ini | 37 ++++++++----------------------------- 4 files changed, 21 insertions(+), 42 deletions(-) delete mode 100644 requirements.txt diff --git a/.travis.yml b/.travis.yml index b6e97e6..559663c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,17 @@ language: python python: - "pypy" + - "2.6" - "2.7" - "3.2" - "3.3" - - "2.6" # command to install dependencies install: - - pip install -r requirements.txt + - pip install -r requirements-dev.txt - pip install coveralls - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse importlib unittest2 astor; fi - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install astor; fi - - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then pip install astor; fi - - python setup.py -q install -# # command to run tests + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi # needs for running tests + - pip install --allow-all-external -e . +# command to run tests script: make travis after_success: coveralls notifications: diff --git a/requirements-dev.txt b/requirements-dev.txt index fc712b3..749f5be 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,11 @@ --r requirements.txt -tox +# test tools nose +tox + +# code quality +flake8 +coverage + +# documentation Pygments>=1.6 Sphinx -coverage diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ab8080a..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Check site / dev for more deps! -flake8 -rply>=0.7.0 diff --git a/tox.ini b/tox.ini index 44c1903..6c280fc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,39 +1,18 @@ [tox] -envlist = py27,pypy,py32,py33,py26,flake8 +envlist = py26,py27,pypy,py32,py33,flake8 +skipsdist = True + [testenv] -commands = nosetests +commands = + pip install --allow-all-external -e . + nosetests deps = - nose - setuptools - rply - -[testenv:pypy] -commands = nosetests -deps = - astor - nose - setuptools - rply - -[testenv:py27] -commands = nosetests -deps = - astor - nose - setuptools - rply + -rrequirements-dev.txt [testenv:py26] deps = - astor - nose - setuptools + {[testenv]deps} unittest2 - importlib - rply [testenv:flake8] -deps = - flake8 - rply commands = flake8 hy bin tests From 4f8ab5af8c7f9603f5791ede622525d5aabe99c4 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Mon, 21 Apr 2014 13:47:31 -0500 Subject: [PATCH 10/15] Changed conf.py to use RTD theme if available --- docs/conf.py | 8 +++++++- requirements-dev.txt | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 4744800..9fe83a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -96,7 +96,13 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +try: + import sphinx_rtd_theme +except ImportError: + html_theme = 'default' +else: + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/requirements-dev.txt b/requirements-dev.txt index 749f5be..88a91e9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,3 +9,4 @@ coverage # documentation Pygments>=1.6 Sphinx +sphinx_rtd_theme From 3f9ae9122e077d0d2b5350fffe562262c75443fb Mon Sep 17 00:00:00 2001 From: Allison Kaptur Date: Tue, 15 Apr 2014 17:27:54 -0400 Subject: [PATCH 11/15] Raise exceptions when no handlers are provided. Also small DRYing in try handling. Previously, writing a bare (try (foo)) would invoke Pokemon exception catching (gotta catch 'em all) instead of the correct behavior, which is to raise the exception if no handler is provided. Note that this is a cute feature of Hy, as a `try` with no `except` is a syntax error. We avoid the syntax error here because we don't use Python's compiler, which is the only thing that can throw Syntax Errors. :D Fixes #555. --- AUTHORS | 1 + hy/compiler.py | 36 +++++++++++++++------------------- tests/native_tests/language.hy | 5 +++++ 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/AUTHORS b/AUTHORS index e5b61cf..847740c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,3 +45,4 @@ * kirbyfan64 * Brendan Curran-Johnson * Ivan Kozik +* Allison Kaptur diff --git a/hy/compiler.py b/hy/compiler.py index 2122ea9..032a49a 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -732,25 +732,10 @@ class HyASTCompiler(object): handler_results += self._compile_catch_expression(e, name) handlers.append(handler_results.stmts.pop()) elif e[0] == HySymbol("else"): - if orelse: - raise HyTypeError( - e, - "`try' cannot have more than one `else'") - else: - orelse = self._compile_branch(e[1:]) - # XXX tempvar magic - orelse += orelse.expr_as_stmt() - orelse = orelse.stmts + orelse = self.try_except_helper(e, HySymbol("else"), orelse) elif e[0] == HySymbol("finally"): - if finalbody: - raise HyTypeError( - e, - "`try' cannot have more than one `finally'") - else: - finalbody = self._compile_branch(e[1:]) - # XXX tempvar magic - finalbody += finalbody.expr_as_stmt() - finalbody = finalbody.stmts + finalbody = self.try_except_helper(e, HySymbol("finally"), + finalbody) else: raise HyTypeError(e, "Unknown expression in `try'") @@ -768,8 +753,8 @@ class HyASTCompiler(object): col_offset=expr.start_column, type=None, name=None, - body=[ast.Pass(lineno=expr.start_line, - col_offset=expr.start_column)])] + body=[ast.Raise(lineno=expr.start_line, + col_offset=expr.start_column)])] ret = handler_results @@ -809,6 +794,17 @@ class HyASTCompiler(object): body=body, orelse=orelse) + returnable + def try_except_helper(self, hy_obj, symbol, accumulated): + if accumulated: + raise HyTypeError( + hy_obj, + "`try' cannot have more than one `%s'" % symbol) + else: + accumulated = self._compile_branch(hy_obj[1:]) + accumulated += accumulated.expr_as_stmt() + accumulated = accumulated.stmts + return accumulated + @builds("except") @builds("catch") def magic_internal_form(self, expr): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index ccf81c5..6995d5c 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -236,6 +236,11 @@ "NATIVE: test do" (do)) +(defn test-bare-try [] (try + (try (raise ValueError)) + (except [ValueError]) + (else (assert false)))) + (defn test-exceptions [] "NATIVE: test Exceptions" From 8e4b21103c1fdd57461af5c9e86e1780309af3df Mon Sep 17 00:00:00 2001 From: pyos Date: Tue, 29 Apr 2014 18:01:14 +0400 Subject: [PATCH 12/15] Reimplement some built-ins in terms of the standard library. As a result: * functions such as `nth` should work correctly on iterators; * `nth` will raise `IndexError` (in a fashion consistent with `get`) when the index is out of bounds; * `take`, etc. will raise `ValueError` instead of returning an ambiguous value if the index is negative; * `map`, `zip`, `range`, `input`, `filter` work the same way (Py3k one) on both Python 2 and 3 (see #523 and #331). --- docs/language/core.rst | 12 ++-- hy/core/language.hy | 121 +++++++++++++-------------------- tests/native_tests/core.hy | 17 +++-- tests/native_tests/language.hy | 2 +- 4 files changed, 65 insertions(+), 87 deletions(-) diff --git a/docs/language/core.rst b/docs/language/core.rst index 46b21b2..f52338d 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -495,8 +495,8 @@ nth Usage: ``(nth coll n)`` Return the `nth` item in a collection, counting from 0. Unlike -``get``, ``nth`` works on both iterators and iterables. Returns ``None`` -if the `n` is outside the range of `coll`. +``get``, ``nth`` works on both iterators and iterables. Raises ``IndexError`` +if the `n` is outside the range of ``coll`` or ``ValueError`` if it's negative. .. code-block:: hy @@ -506,8 +506,10 @@ if the `n` is outside the range of `coll`. => (nth [1 2 4 7] 3) 7 - => (none? (nth [1 2 4 7] 5)) - True + => (nth [1 2 4 7] 5) + Traceback (most recent call last): + ... + IndexError: 5 => (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2)) 5 @@ -764,6 +766,7 @@ drop Usage: ``(drop n coll)`` Return an iterator, skipping the first ``n`` members of ``coll`` +Raises ``ValueError`` if ``n`` is negative. .. code-block:: hy @@ -924,6 +927,7 @@ take Usage: ``(take n coll)`` Return an iterator containing the first ``n`` members of ``coll``. +Raises ``ValueError`` if ``n`` is negative. .. code-block:: hy diff --git a/hy/core/language.hy b/hy/core/language.hy index 0df4b44..ff44e80 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -23,7 +23,9 @@ ;;;; to make functional programming slightly easier. ;;;; - +(import itertools) +(import functools) +(import collections) (import [hy._compat [long-type]]) ; long for python2, int for python3 (import [hy.models.cons [HyCons]]) @@ -49,15 +51,6 @@ (and (instance? (type :foo) k) (.startswith k (get :foo 0)))) -(defn cycle [coll] - "Yield an infinite repetition of the items in coll" - (setv seen []) - (for* [x coll] - (yield x) - (.append seen x)) - (while seen - (for* [x seen] - (yield x)))) (defn dec [n] "Decrement n by 1" @@ -87,22 +80,36 @@ (yield val) (.add seen val)))))) +(if-python2 + (do + (setv filterfalse itertools.ifilterfalse) + (setv zip_longest itertools.izip_longest) + (setv filter itertools.ifilter) + (setv map itertools.imap) + (setv zip itertools.izip) + (setv range xrange) + (setv input raw_input)) + (do + (setv reduce functools.reduce) + (setv filterfalse itertools.filterfalse) + (setv zip_longest itertools.zip_longest) + ; Someone can import these directly from `hy.core.language`; + ; we'll make some duplicates. + (setv filter filter) + (setv map map) + (setv zip zip) + (setv range range) + (setv input input))) + +(setv cycle itertools.cycle) +(setv repeat itertools.repeat) +(setv drop-while itertools.dropwhile) +(setv take-while itertools.takewhile) +(setv zipwith map) + (defn drop [count coll] "Drop `count` elements from `coll` and yield back the rest" - (let [[citer (iter coll)]] - (try (for* [i (range count)] - (next citer)) - (catch [StopIteration])) - citer)) - -(defn drop-while [pred coll] - "Drop all elements of `coll` until `pred` is False" - (let [[citer (iter coll)]] - (for* [val citer] - (if (not (pred val)) - (do (yield val) (break)))) - (for* [val citer] - (yield val)))) + (itertools.islice coll count nil)) (defn empty? [coll] "Return True if `coll` is empty" @@ -126,13 +133,6 @@ (if (not (hasattr tree attr)) (setattr tree attr 1)))) -(defn filter [pred coll] - "Return all elements from `coll` that pass `pred`" - (let [[citer (iter coll)]] - (for* [val citer] - (if (pred val) - (yield val))))) - (defn flatten [coll] "Return a single flat list expanding all members of coll" (if (coll? coll) @@ -174,7 +174,7 @@ (defn first [coll] "Return first item from `coll`" - (get coll 0)) + (nth coll 0)) (defn identity [x] "Returns the argument unchanged" @@ -199,14 +199,13 @@ (defn integer-char? [x] "Return True if char `x` parses as an integer" (try - (integer? (int x)) - (catch [e ValueError] False) - (catch [e TypeError] False))) + (integer? (int x)) + (catch [e ValueError] False) + (catch [e TypeError] False))) (defn iterable? [x] "Return true if x is iterable" - (try (do (iter x) true) - (catch [Exception] false))) + (isinstance x collections.Iterable)) (defn iterate [f x] (setv val x) @@ -216,8 +215,7 @@ (defn iterator? [x] "Return true if x is an iterator" - (try (= x (iter x)) - (catch [TypeError] false))) + (isinstance x collections.Iterator)) (defn list* [hd &rest tl] "Return a dotted list construed from the elements of the argument" @@ -258,13 +256,9 @@ (defn nth [coll index] "Return nth item in collection or sequence, counting from 0" - (if (not (neg? index)) - (if (iterable? coll) - (try (get (list (take 1 (drop index coll))) 0) - (catch [IndexError] None)) - (try (get coll index) - (catch [IndexError] None))) - None)) + (try + (next (drop index coll)) + (catch [e StopIteration] (raise (IndexError index))))) (defn odd? [n] "Return true if n is an odd number" @@ -285,14 +279,7 @@ (defn rest [coll] "Get all the elements of a coll, except the first." - (slice coll 1)) - -(defn repeat [x &optional n] - "Yield x forever or optionally n times" - (if (none? n) - (setv dispatch (fn [] (while true (yield x)))) - (setv dispatch (fn [] (for* [_ (range n)] (yield x))))) - (dispatch)) + (drop 1 coll)) (defn repeatedly [func] "Yield result of running func repeatedly" @@ -301,7 +288,7 @@ (defn second [coll] "Return second item from `coll`" - (get coll 1)) + (nth coll 1)) (defn some [pred coll] "Return true if (pred x) is logical true for any x in coll, else false" @@ -322,9 +309,7 @@ (defn take [count coll] "Take `count` elements from `coll`, or the whole set if the total number of entries in `coll` is less than `count`." - (let [[citer (iter coll)]] - (for* [_ (range count)] - (yield (next citer))))) + (itertools.islice coll nil count)) (defn take-nth [n coll] "Return every nth member of coll @@ -337,29 +322,15 @@ (next citer)))) (raise (ValueError "n must be positive")))) -(defn take-while [pred coll] - "Take all elements while `pred` is true" - (let [[citer (iter coll)]] - (for* [val citer] - (if (pred val) - (yield val) - (break))))) - (defn zero? [n] "Return true if n is 0" (_numeric_check n) (= n 0)) -(defn zipwith [func &rest lists] - "Zip the contents of several lists and map a function to the result" - (do - (import functools) - (map (functools.partial (fn [f args] (apply f args)) func) (apply zip lists)))) - (def *exports* '[calling-module-name coll? cons cons? cycle dec distinct disassemble drop drop-while empty? even? every? first filter flatten float? gensym identity inc instance? integer integer? integer-char? iterable? iterate iterator? keyword? - list* macroexpand macroexpand-1 neg? nil? none? nth - numeric? odd? pos? remove repeat repeatedly rest second - some string string? take take-nth take-while zero? zipwith]) + list* macroexpand macroexpand-1 map neg? nil? none? nth + numeric? odd? pos? range remove repeat repeatedly rest second + some string string? take take-nth take-while zero? zip zipwith]) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 98e403d..c6d8306 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -82,8 +82,8 @@ (assert-equal res [None 4 5]) (setv res (list (drop 0 [1 2 3 4 5]))) (assert-equal res [1 2 3 4 5]) - (setv res (list (drop -1 [1 2 3 4 5]))) - (assert-equal res [1 2 3 4 5]) + (try (do (list (drop -1 [1 2 3 4 5])) (assert False)) + (catch [e [ValueError]] nil)) (setv res (list (drop 6 (iter [1 2 3 4 5])))) (assert-equal res []) (setv res (list (take 5 (drop 2 (iterate inc 0))))) @@ -335,12 +335,15 @@ "NATIVE: testing the nth function" (assert-equal 2 (nth [1 2 4 7] 1)) (assert-equal 7 (nth [1 2 4 7] 3)) - (assert-true (none? (nth [1 2 4 7] 5))) - (assert-true (none? (nth [1 2 4 7] -1))) + (try (do (nth [1 2 4 7] 5) (assert False)) + (catch [e [IndexError]] nil)) + (try (do (nth [1 2 4 7] -1) (assert False)) + (catch [e [ValueError]] nil)) ;; now for iterators (assert-equal 2 (nth (iter [1 2 4 7]) 1)) (assert-equal 7 (nth (iter [1 2 4 7]) 3)) - (assert-true (none? (nth (iter [1 2 4 7]) -1))) + (try (do (nth (iter [1 2 4 7]) -1) (assert False)) + (catch [e [ValueError]] nil)) (assert-equal 5 (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2))) (defn test-numeric? [] @@ -429,8 +432,8 @@ (assert-equal res ["s" "s" "s" "s"]) (setv res (list (take 0 (repeat "s")))) (assert-equal res []) - (setv res (list (take -1 (repeat "s")))) - (assert-equal res []) + (try (do (list (take -1 (repeat "s"))) (assert False)) + (catch [e [ValueError]] nil)) (setv res (list (take 6 [1 2 None 4]))) (assert-equal res [1 2 None 4])) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 6995d5c..f9f1181 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -474,7 +474,7 @@ (defn test-rest [] "NATIVE: test rest" - (assert (= (rest [1 2 3 4 5]) [2 3 4 5]))) + (assert (= (list (rest [1 2 3 4 5])) [2 3 4 5]))) (defn test-importas [] From f02e0447192db7be3de79ed8167c3ae4c0658206 Mon Sep 17 00:00:00 2001 From: pyos Date: Tue, 29 Apr 2014 18:04:21 +0400 Subject: [PATCH 13/15] Move the documentation for `zipwith` to the correct place. --- docs/language/api.rst | 22 ---------------------- docs/language/core.rst | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index fe3575e..e58db48 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1273,25 +1273,3 @@ yield-from want your coroutine to be able to delegate its processes to another coroutine, say if using something fancy like `asyncio `_. - - -.. _zipwith: - -zipwith -------- - -.. versionadded:: 0.10.0 - -`zipwith` zips multiple lists and maps the given function over the result. It is -equilavent to calling ``zip``, followed by calling ``map`` on the result. - -In the following example, `zipwith` is used to add the contents of two lists -together. The equilavent ``map`` and ``zip`` calls follow. - -.. code-block:: clj - - => (import operator.add) - => (zipwith operator.add [1 2 3] [4 5 6]) ; using zipwith - [5, 7, 9] - => (map operator.add (zip [1 2 3] [4 5 6])) ; using map+zip - [5, 7, 9] diff --git a/docs/language/core.rst b/docs/language/core.rst index f52338d..42070cf 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -983,3 +983,21 @@ Return an iterator from ``coll`` as long as predicate, ``pred`` returns True. => (list (take-while neg? [ 1 2 3 -4 5])) [] + +.. _zipwith: + +zipwith +------- + +.. versionadded:: 0.9.13 + +Usage: ``(zipwith fn coll ...)`` + +Equivalent to ``zip``, but uses a multi-argument function instead of creating a tuple. +If ``zipwith`` is called with N collections, then ``fn`` must accept N arguments. + +.. code-block:: clojure + + => (import operator) + => (list (zipwith operator.add [1 2 3] [4 5 6])) + [5, 7, 9] From cdea12b2764d5c417df55a9499ad3bde17fb3e0c Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 10 Apr 2014 07:45:11 +0300 Subject: [PATCH 14/15] Make hy2py public. --- bin/hy2py | 40 ------------------------------------ docs/language/cli.rst | 30 +++++++++++++++++++++++++++ hy/cmdline.py | 48 +++++++++++++++++++++++++++++++++++++++++-- setup.py | 3 ++- tests/test_bin.py | 2 +- 5 files changed, 79 insertions(+), 44 deletions(-) delete mode 100755 bin/hy2py diff --git a/bin/hy2py b/bin/hy2py deleted file mode 100755 index 69b151b..0000000 --- a/bin/hy2py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function - -from hy.importer import import_file_to_ast, import_file_to_hst - -import argparse -import sys - -import astor.codegen - -module_name = "" - -parser = argparse.ArgumentParser( - prog="hy2py", - usage="%(prog)s [options] FILE", - formatter_class=argparse.RawDescriptionHelpFormatter) -parser.add_argument("--with-source", "-s", action="store_true", - help="Show the parsed source structure") -parser.add_argument("--with-ast", "-a", action="store_true", - help="Show the generated AST") -parser.add_argument("--without-python", "-np", action="store_true", - help="Do not show the python code generated from the AST") -parser.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) - -options = parser.parse_args(sys.argv[1:]) - -if options.with_source: - hst = import_file_to_hst(options.args[0]) - print(hst) - print() - print() - -_ast = import_file_to_ast(options.args[0], module_name) -if options.with_ast: - print(astor.dump(_ast)) - print() - print() - -if not options.without_python: - print(astor.codegen.to_source(_ast)) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index ab9d8aa..98e853e 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -2,6 +2,8 @@ Command Line Interface ====================== +.. _hy: + hy -- @@ -46,6 +48,8 @@ Command line options Print the Hy version number and exit. +.. _hyc: + hyc --- @@ -71,3 +75,29 @@ Command line options $ hyc hyname.hy $ python hyname.pyc Hy Afroman! + + +.. _hy2py: + +hy2py +----- + +.. versionadded:: 0.10.1 + +Command line options +^^^^^^^^^^^^^^^^^^^^ + +.. cmdoption:: -s + --with-source + + Show the parsed source structure. + +.. cmdoption:: -a + --with-ast + + Show the generated AST. + +.. cmdoption:: -np + --without-python + + Do not show the Python code generated from the AST. diff --git a/hy/cmdline.py b/hy/cmdline.py index 98f10cd..6ccbb8f 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -25,16 +25,21 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from __future__ import print_function + import argparse import code import ast import sys +import astor.codegen + import hy from hy.lex import LexException, PrematureEndOfInput, tokenize from hy.compiler import hy_compile, HyTypeError -from hy.importer import ast_compile, import_buffer_to_module +from hy.importer import (ast_compile, import_buffer_to_module, + import_file_to_ast, import_file_to_hst) from hy.completer import completion from hy.macros import macro, require @@ -66,7 +71,6 @@ builtins.exit = HyQuitter('exit') def print_python_code(_ast): - import astor.codegen # astor cannot handle ast.Interactive, so disguise it as a module _ast_for_print = ast.Module() _ast_for_print.body = _ast.body @@ -313,3 +317,43 @@ def hyc_main(): sys.stderr.write("hyc: Can't open file '%s': [Errno %d] %s\n" % (x.filename, x.errno, x.strerror)) sys.exit(x.errno) + + +# entry point for cmd line script "hy2py" +def hy2py_main(): + module_name = "" + + options = dict(prog="hy2py", usage="%(prog)s [options] FILE", + formatter_class=argparse.RawDescriptionHelpFormatter) + parser = argparse.ArgumentParser(**options) + parser.add_argument("--with-source", "-s", action="store_true", + help="Show the parsed source structure") + parser.add_argument("--with-ast", "-a", action="store_true", + help="Show the generated AST") + parser.add_argument("--without-python", "-np", action="store_true", + help=("Do not show the Python code generated " + "from the AST")) + parser.add_argument('args', nargs=argparse.REMAINDER, + help=argparse.SUPPRESS) + + options = parser.parse_args(sys.argv[1:]) + + if not options.args: + parser.exit(1, parser.format_help()) + + if options.with_source: + hst = import_file_to_hst(options.args[0]) + print(hst) + print() + print() + + _ast = import_file_to_ast(options.args[0], module_name) + if options.with_ast: + print(astor.dump(_ast)) + print() + print() + + if not options.without_python: + print(astor.codegen.to_source(_ast)) + + parser.exit(0) diff --git a/setup.py b/setup.py index 40bb4e0..b357a9e 100755 --- a/setup.py +++ b/setup.py @@ -59,7 +59,8 @@ setup( entry_points={ 'console_scripts': [ 'hy = hy.cmdline:hy_main', - 'hyc = hy.cmdline:hyc_main' + 'hyc = hy.cmdline:hyc_main', + 'hy2py = hy.cmdline:hy2py_main', ] }, packages=find_packages(exclude=['tests*']), diff --git a/tests/test_bin.py b/tests/test_bin.py index eb0d595..ed74ae8 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -129,7 +129,7 @@ def test_hy2py(): for f in filenames: if f.endswith(".hy"): i += 1 - ret = run_cmd("python bin/hy2py -s -a " + ret = run_cmd("hy2py -s -a " + os.path.join(dirpath, f)) assert ret[0] == 0, f assert len(ret[1]) > 1, f From 229d782ce94eb5a6b38a7f61466b854c8c04152b Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Wed, 30 Apr 2014 09:24:19 +0300 Subject: [PATCH 15/15] Enable Python 3.4 on Travis CI. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 559663c..22c320e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - "2.7" - "3.2" - "3.3" + - "3.4" # command to install dependencies install: - pip install -r requirements-dev.txt