diff --git a/AUTHORS b/AUTHORS index e49a99e..4fae5f8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,3 +4,6 @@ * Christopher Allan Webber * Will Kahn-Greene * James King +* Julien Danjou +* Nicolas Dandrimont +* Gergely Nagy diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index e6c6361..0000000 --- a/ChangeLog +++ /dev/null @@ -1,33 +0,0 @@ -2013-04-01 Paul Tagliamonte - -* hy/compiler.py (compile_print_expression): Add routines for not creating -an ast.Print in Python 3.x, it's since become an ast.Call. -* Cleaned up the tests a bit, and added some coverage to some uncovered -modules -* hy/compiler.py (compile_do_expression): Add progn as an alias -(compile_def_expression): Add setf / setv as an alias for simple assignment -(compile_list_comrehension): Added list comprehensions. - -2013-03-29 Paul Tagliamonte - -* New release, 0.9.3. - -2013-03-27 Paul Tagliamonte - -* hy/compiler.py (compile_tuple): Add routines for tuples. -(compile_def_expression): add Tuple checking -(compile_for_expression): same -(compile_with_as_expression): same - -2013-03-25 Paul Tagliamonte - -* bin/hy (global): Added readline support to the hy REPL. History should -now be stored in ~/.hy-history -(global): added paren-matching bits, hacked in hard. Better solution? - -2013-03-24 Paul Tagliamonte - -* The release before this was version 0.9.2. - -* Initial ChangeLog entry. There've been a lot of changes, but I won't bother -to fake history here. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1e4a5f8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +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. diff --git a/Makefile b/Makefile index e868f85..25a2517 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,9 @@ site: docs: make -C docs html +upload: r + python setup.py sdist upload + full: d tox site docs venv: diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..2a26aed --- /dev/null +++ b/NEWS @@ -0,0 +1,122 @@ +Changes from Hy 0.9.4 + + [ Syntax Fixes ] + + * `try' now accepts `else': (JD) + (try BODY + (except [] BODY) + (else BODY)) + + +Changes from Hy 0.9.4 + + [ Syntax Fixes ] + + * Statements in the `fn' path early will not return anymore. (PT) + * Added "not" as the inline "not" operator. It's advised to still + use "not-in" or "is-not" rather then nesting. (JD) + * `let' macro added (PT) + * Added "~" as the "invert" operator. (JD) + * `catch' now accepts a new format: (JD) + (catch [] BODY) + (catch [Exception] BODY) + (catch [e Exception] BODY) + (catch [e [Exception1 Exception2]] BODY) + * With's syntax was fixed to match the rest of the code. It's now: (PT) + (with [name context-managed-fn] BODY) + (with [context-managed-fn] BODY) + + [ Language Changes ] + + * Added `and' and `or' (GN) + * Added the tail threading macro (->>) (PT) + * UTF encoded symbols are allowed, but mangled. All Hy source is now + presumed to be UTF-8. (JD + PT) + * Better builtin signature checking (JD) + * If hoisting (for things like printing the return of an if statement) + have been added. '(print (if true true true))' (PT) + + [ Documentation ] + + * Initial documentation added to the source tree. (PT) + + +Changes from Hy 0.9.3 + + [ Syntax Fixes ] + + * Nested (do) expressions no longer break Hy (PT) + * `progn' is now a valid alias for `do' (PT) + * `defun' is now a valid alias for `defn' (PT) + * Added two new escapes for \ and " (PT) + + [ Language Changes ] + + * Show a traceback when a compile-error bubbles up in the Hy REPL (PT) + * `setf' / `setv' added, the behavior of `def` may change in the future. + * `print' no longer breaks in Python 3.x (PT) + * Added `list-comp' list comprehensions. (PT) + * Function hoisting (for things like inline invocation of functions, + e.g. '((fn [] (print "hi!")))' has been added. (PT) + * `while' form added. (ND) + (while [CONDITIONAL] BODY) + + [ Documentation ] + + * Initial docs added. (WKG + CW) + + +Changes from Hy 0.9.2 + + [ General Enhancements ] + + * hy.__main__ added, `python -m hy' will now allow a hy shim into existing + Python scripts. (PT) + + [ Language Changes ] + + * `import-as' added to allow for importing modules. (Amrut Joshi) + * `slice' added to slice up arrays. (PT) + * `with-as' added to allow for context managed bits. (PT) + * `%' added to do Modulo. (PT) + * Tuples added with the '(, foo bar)' syntax. (PT) + * `car' / `first' added. (PT) + * `cdr' / `rest' added. (PT) + * hy --> .pyc compiler added. (PT) + * Completer added for the REPL Readline autocompletion. (PT) + * Merge the `meth' macros into hy.contrib. (PT) + * Changed __repr__ to match Hy source conventions. (PT) + * 2.6 support restored. (PT) + + +Changes from Hy 0.9.1 + + [ General Enhancements ] + + * Hy REPL added. (PT) + * Doc templates added. (PT) + + [ Language Changes ] + + * Add `pass' (PT) + * Add `yield' (PT) + * Moved `for' to a macro, and move `foreach' to old `for'. (PT) + * Add the threading macro (`->'). (PT) + * Add "earmufs" in. (tenach) + * Add comments in (PT) + + +Changes from Hy 0.9.0 + + [ Language Changes ] + + * Add `throw' (PT) + * Add `try' (PT) + * add `catch' (PT) + + +Changes from Hy 0.8.2 + + [ Notes ] + + * Complete rewrite of old-hy. (PT) diff --git a/README.md b/README.md index 36dffe1..6c398e7 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Lisp and Python should love each other. Let's make it happen. [![Build Status](https://travis-ci.org/paultag/hy.png?branch=master)](https://travis-ci.org/paultag/hy) + Hylarious Hacks --------------- @@ -28,3 +29,13 @@ Well, I wrote Hy to help people realize one thing about Python: It's really goddamn awesome. Oh, and lisps are neat. + + +Project +------- + +* Code: https://github.com/paultag/hy +* Docs: http://hy.rtfd.org/ +* Quickstart: http://hy.rtfd.org/en/latest/quickstart.html +* Bug reports: We have no bugs! Your bugs are your own! (https://github.com/paultag/hy/issues) +* License: MIT (Expat) diff --git a/TODO b/TODO index bf2c64d..13d0ff4 100644 --- a/TODO +++ b/TODO @@ -9,9 +9,6 @@ + Use (def) to imply global foo - - New Builtins: - + While - - New macros: + loop @@ -31,3 +28,5 @@ - core tests (odd? even? true? false?) which build out to a lambda / function / expr + + - add -d flag (debug) for hy REPL based on @jd's try/catch diff --git a/bin/hy b/bin/hy index 3998d3b..4167e68 100755 --- a/bin/hy +++ b/bin/hy @@ -51,8 +51,12 @@ class HyREPL(code.InteractiveConsole): tokens = process(_machine.nodes) _machine = Machine(Idle, 1, 0) - _ast = hy_compile(tokens, root=ast.Interactive) - code = compile(_ast, filename, symbol) + try: + _ast = hy_compile(tokens, root=ast.Interactive) + code = compile(_ast, filename, symbol) + except Exception: + self.showtraceback() + return False self.runcode(code) return False diff --git a/docs/_static/CC0_1.0.txt b/docs/_static/CC0_1.0.txt new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/docs/_static/CC0_1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/docs/_static/hy_logo-about.txt b/docs/_static/hy_logo-about.txt new file mode 100644 index 0000000..203a1c3 --- /dev/null +++ b/docs/_static/hy_logo-about.txt @@ -0,0 +1,15 @@ +Hy Logo is TeXGyreBonum font with the two parentheses, and two copies +of the lambda character all flipped around and rotated! + +Aside from the font design (which was not done by any Hy authors!) the +Hy logo is authored by Christopher Allan Webber in 2013 and released +into the public domain under CC0... see CC0_1.0.txt! + + To the extent possible under law, the author(s) have dedicated all + copyright and related and neighboring rights to this logo to + the public domain worldwide. This software is distributed without + any warranty. + + You should have received a copy of the CC0 Public Domain + Dedication along with this software. If not, see + . diff --git a/docs/_static/hy_logo-paths.svg b/docs/_static/hy_logo-paths.svg new file mode 100644 index 0000000..392c136 --- /dev/null +++ b/docs/_static/hy_logo-paths.svg @@ -0,0 +1,111 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/hy_logo-smaller.png b/docs/_static/hy_logo-smaller.png new file mode 100644 index 0000000..3fd7b4d Binary files /dev/null and b/docs/_static/hy_logo-smaller.png differ diff --git a/docs/_static/hy_logo.png b/docs/_static/hy_logo.png new file mode 100644 index 0000000..ee62bd0 Binary files /dev/null and b/docs/_static/hy_logo.png differ diff --git a/docs/_static/hy_logo.svg b/docs/_static/hy_logo.svg new file mode 100644 index 0000000..0b9b3c3 --- /dev/null +++ b/docs/_static/hy_logo.svg @@ -0,0 +1,125 @@ + + + + + + + + + + image/svg+xml + + + + + + + + λ + λ + ( + ) + + diff --git a/docs/hacking.rst b/docs/hacking.rst new file mode 100644 index 0000000..404f4f3 --- /dev/null +++ b/docs/hacking.rst @@ -0,0 +1,68 @@ +=============== + Hacking on hy +=============== + +Join our hyve! +============== + +Please come hack on hy! + +Please come hang out with us on ``#hy`` on ``irc.freenode.net``! + +Please talk about it on Twitter with the ``#hy`` hashtag! + +Please blog about it! + +Please don't spraypaint it on your neighbor's fence (without asking nicely)! + + +Hack! +===== + +Do this: + +1. create a `Python virtual environment + `_ +2. (optional) go to https://github.com/paultag/hy and fork it +3. get the source code:: + + $ git clone git://github.com/paultag/hy.git + + (or use your fork) +4. install for hacking:: + + $ python setup.py develop + +5. install other develop-y requirements:: + + $ pip install -r requirements-dev.txt + +6. do awesome things; make someone shriek in delight/disgust at what + you have wrought + + +Test! +===== + +Tests are located in ``tests/``. We use `nose +`_. + +To run the tests:: + + $ nosetests + +Write tests---tests are good! + + +Document! +========= + +Documentation is located in ``docs/``. We use `Sphinx +`_. + +To build the docs in html:: + + $ cd docs + $ make html + +Write docs---docs are good! Even this doc! diff --git a/docs/index.rst b/docs/index.rst index 57a77b1..2e3d41e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,10 @@ Welcome to Hy's documentation! ============================== +.. image:: _static/hy_logo-smaller.png + :alt: Hy logo + :align: left + Welcome to `Hy `_! Hy is a wonderful dialect of Lisp that's embedded in Python. Since Hy transforms its lisp code into the python Abstract Syntax @@ -32,5 +36,7 @@ Contents: .. toctree:: :maxdepth: 3 + quickstart + hacking + tutorial language/index -.. library/index diff --git a/docs/language/api.rst b/docs/language/api.rst new file mode 100644 index 0000000..a59a450 --- /dev/null +++ b/docs/language/api.rst @@ -0,0 +1,101 @@ +================= +Hy (the language) +================= + + +.. warning:: + This is incomplete; please consider contributing to the documentation + effort. + + +Theory of Hy +============ + +Hy maintains, over everything else, 100% compatibility in both directions +with Python it's self. All Hy code follows a few simple rules. Memorize +this, it's going to come in handy. + +These rules help make sure code is idiomatic and interface-able in both +languages. + + + * Symbols in earmufs will be translated to the uppercased version of that + string. For example, `*foo*` will become `FOO`. + + * UTF-8 entities will be encoded using + `punycode `_ and prefixed with + `__hy_`. For instance, `⚘` will become `__hy_w7h`, and `♥` will become + `__hy_g6h`. + + * Symbols that contain dashes will have them replaced with underscores. For + example, `render-template` will become `render_template`. + + +Builtins +======== + +Hy features a number special forms that are used to help generate +correct Python AST. The following are "special" forms, which may have +behavior that's slightly unexpected in some situations. + +do / progn +---------- + +the `do` or `progn` forms can be used in full code branches. What that means +is basically `(do)` and `(progn)` can only be used where a Python expression +can be used. These forms don't actually allow you to break Pythonic internals +such as `lambda` or `list-comp`, where you can only have one expression. + + +Some example usage + +.. code-block:: clj + + (if true + (do (print "Side effects rock!") + (print "Yeah, really!"))) + +`do` can accept any number of arguments, from 1 to n. + + +throw / raise +------------- + +the `throw` or `raise` forms can be used to raise an Exception at runtime. + + +Example usage + +.. code-block:: clj + + (throw) + ; re-rase the last exception + + (throw IOError) + ; Throw an IOError + + (throw (IOError "foobar")) + ; Throw an IOError("foobar") + + +`throw` can acccept a single argument (an `Exception` class or instance), or +no arguments to re-raise the last Exception. + + +try +--- + +.. TODO:: + Document the else / finally syntax. + +the `try` form is used to start a `try` / `catch` block. The form is used +as follows + +.. code-block:: clj + + (try + (error-prone-function) + (catch [e SomeException] (err "It sucks!"))) + +`try` must contain at least one `catch` block, and may optionally have an +`else` or `finally` block. diff --git a/docs/language/index.rst b/docs/language/index.rst index e0e085a..6ca1ffd 100644 --- a/docs/language/index.rst +++ b/docs/language/index.rst @@ -1,392 +1,11 @@ -Language Spec -============= -This bit covers a bit about Hy's lovable quirks and eccentricities. +Documentation Index +=================== -In a nutshell, Hy is a lisp dialect, but one that converts its -structure into Python... literally a conversion into Python's abstract -syntax tree! (Or to put it in more crude terms, Hy is lisp-stick on a -python!) +Contents: -This is pretty cool because it means Hy is several things: +.. toctree:: + :maxdepth: 3 - - A lisp that feels very pythonic - - For lispers, a great way to use lisp's crazy powers but in the wide - world of Python's libraries (why yes, you now can write a Django - application in lisp!) - - For pythonistas, a great way to start exploring lisp, from the - comfort of python! - - For everyone: a pleasant language that has a lot of neat ideas! - - -Basic intro to lisp for pythonistas ------------------------------------ - -Okay, maybe you've never used lisp before, but you've used python! - -A "hello world" in hy is actually super simple. Let's try it: - -.. code-block:: clj - - (print "hello world") - -See? Easy! As you may have guessed, this is the same as the python -version of:: - - print "hello world" - -To add up some super simple math, we could do: - -.. code-block:: clj - - (+ 1 3) - -Which would return 4 and would be the equivalent of: - -.. code-block:: clj - - 1 + 3 - -What you'll notice is that the first item in the list is the function -being called and the rest of the arguments are the arguments being -passed in. In fact, in hy (as with most lisps) we can pass in -multiple arguments to the plus operator: - -.. code-block:: clj - - (+ 1 3 55) - -Which would return 59. - -Maybe you've heard of lisp before but don't know much about it. Lisp -isn't as hard as you might think, and hy inherits from python, so hy -is a great way to start learning lisp. The main thing that's obvious -about lisp is that there's a lot of parentheses. This might seem -confusing at first, but it isn't so hard. Let's look at some simple -math that's wrapped in a bunch of parentheses that we could enter into -the hy interpreter: - -.. code-block:: clj - - (setv result (- (/ (+ 1 3 88) 2) 8)) - -This would return 37. But why? Well, we could look at the equivalent -expression in python:: - - result = ((1 + 3 + 88) / 2) - 8 - -If you were to try to figure out how the above were to work in python, -you'd of course figure out the results by solving each inner -parenthesis. That's the same basic idea in hy. Let's try this -exercise first in python:: - - result = ((1 + 3 + 88) / 2) - 8 - # simplified to... - result = (92 / 2) - 8 - # simplified to... - result = 46 - 8 - # simplified to... - result = 38 - -Now let's try the same thing in hy: - -.. code-block:: clj - - (setv result (- (/ (+ 1 3 88) 2) 8)) - ; simplified to... - (setv result (- (/ 92 2) 8)) - ; simplified to... - (setv result (- 46 8)) - ; simplified to... - (setv result 38) - -As you probably guessed, this last expression with "setv" means to -assign the variable "result" to 38. - -See? Not too hard! - -This is the basic premise of lisp... lisp stands for "list -processing"... this means that the structure of the program is -actually lists of lists. (If you're familiar with python lists, -imagine the entire same structure as above but with square brackets -instead, any you'll be able to see the structure above as both a -program and a datastructure.) This is easier to understand with more -examples, so let's write a simple python program and test it and then -show the equivalent hy program:: - - def simple_conversation(): - print "Hello! I'd like to get to know you. Tell me about yourself!" - name = raw_input("What is your name? ") - age = raw_input("What is your age? ") - print "Hello " + name + "! I see you are " + age + " years old." - - simple_conversation() - -If we ran this program, it might go like:: - - Hello! I'd like to get to know you. Tell me about yourself! - What is your name? Gary - What is your age? 38 - Hello Gary! I see you are 38 years old. - -Now let's look at the equivalent hy program: - -.. code-block:: clj - - (defn simple-conversation [] - (print "Hello! I'd like to get to know you. Tell me about yourself!") - (setv name (raw_input "What is your name? ")) - (setv age (raw_input "What is your age? ")) - (print (+ "Hello " name "! I see you are " - age " years old."))) - - (simple-conversation) - -If you look at the above program, as long as you remember that the -first element in each list of the program is the function (or -macro... we'll get to those later) being called and that the rest are -the arguments, it's pretty easy to figure out what this all means. -(As you probably also guessed, defn is the hy method of defining -methods.) - -Still, lots of people find this confusing at first because there's so -many parentheses, but there are plenty of things that can help make -this easier: keep indentation nice and use an editor with parenthesis -matching (this will help you figure out what each parenthesis pairs up -with) and things will start to feel comfortable. - -There are some advantages to having a code structure that's actually a -very simple datastructure as the core of lisp is based on. For one -thing, it means that your programs are easy to parse and that the -entire actual structure of the program is very clearly exposed to you. -(There's an extra step in hy where the structure you see is converted -to python's own representations... in more "pure" lisps such as common -lisp or emacs lisp, the data structure you see for the code and the -data structure that is executed is much more literally close.) - -Another implication of this is macros: if a program's structure is a -simple data structure, that means you can write code that can write -code very easily, meaning that implementing entirely new language -features can be very fast. Previous to hy, this wasn't very possible -for python programmers... now you too can make use of macros' -incredible power (just be careful to not aim them footward)! - - -Hy is python flavored lisp (or vice versa?) -------------------------------------------- - -Hy converts to python's own abstract syntax tree, so you'll soon start -to find that all the familiar power of python is at your fingertips. - -You have full access to python's data types and standard library in -hy. Let's experiment with this in the hy interpreter:: - - => [1 2 3] - [1, 2, 3] - => {"dog" "bark" - ... "cat" "meow"} - ... - {'dog': 'bark', 'cat': 'meow'} - -(You may notice that at present, the common lisp method of quoting -things like so: - -.. code-block:: clj - - '(1 2 3) - -does not work. Instead, use square brackets as above.) - -You also have access to all the builtin types' nice methods:: - - => (.strip " fooooo ") - "fooooo" - -What's this? Yes indeed, this is precisely the same as:: - - " fooooo ".strip() - -That's right... lisp with dot notation! If we have this string -assigned as a variable, we can also do the following: - -.. code-block:: clj - - (setv this-string " fooooo ") - (this-string.strip) - -What about conditionals?: - -.. code-block:: clj - - (if (try-some-thing) - (print "this is if true") - (print "this is if false")) - -As you can tell above, the first argument to if is a truth test, the -second argument is a body if true, and the third argument (optional!) -is if false (ie, "else"!). - -If you need to do more complex conditionals, you'll find that you -don't have elif available in hy. Instead, you should use something -called "cond". In python, you might do something like:: - - somevar = 33 - if somevar > 50: - print "That variable is too big!" - elif somevar < 10: - print "That variable is too small!" - else: - print "That variable is jussssst right!" - -In hy, you would do: - -.. code-block:: clj - - (cond - ((> somevar 50) - (print "That variable is too big!")) - ((< somevar 10) - (print "That variable is too small!")) - (true - (print "That variable is jussssst right!"))) - -What you'll notice is that cond switches off between a some 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 -for "true"... that's because true will always be true, so if we get -this far, we'll always run that one! - -You might notice above that if you have code like: - -.. code-block:: clj - - (if some-condition - (body-if-true) - (body-if-false)) - -But wait! What if you want to execute more than one statment in the -body of one of these? - -You can do the following: - -.. code-block:: clj - - (if (try-some-thing) - (do - (print "this is if true") - (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 statments. If you're -familiar with other lisps, this is the equivalent of "progn" -elsewhere. - -Comments start with semicolons: - -.. code-block:: clj - - (print "this will run") - ; (print "but this will not") - (+ 1 2 3) ; we'll execute the addition, but not this comment! - -Looping is not hard but has a kind of special structure. In python, -we might do:: - - for i in range(10): - print "'i' is now at " + str(i) - -The equivalent in hy would be: - -.. code-block:: clj - - (for (i (range 10)) - (print (+ "'i' is now at " (str i)))) - - -You can also import and make use of various python libraries. For -example: - -.. code-block:: clj - - (import os) - - (if (os.path.isdir "/tmp/somedir") - (os.mkdir "/tmp/somedir/anotherdir") - (print "Hey, that path isn't there!")) - -Comments start with semicolons: - -.. code-block:: clj - - (print "this will run") - ; (print "but this will not") - (+ 1 2 3) ; we'll execute the addition, but not this comment! - -And yes, we do have lisp comprehensions! In Python you might do:: - - odds_squared = [ - pow(num, 2) - for num in range(100) - if num % 2 == 1] - -In hy, you could do these like: - -.. code-block:: clj - - ; and a little more complex - (setv odds-squared - (list-comp - (pow num 2) - (num (range 100)) - (= (% num 2) 1)) - - -Protips! --------- - -Hy also features something known as the "threading macro", a really neat -feature of Clojure's. The "threading macro" (written as "->"), is used -to avoid deep nesting of expressions. - -The threading macro inserts each expression into the next expression's first -argument place. - -Let's take the classic: - -.. code-block:: clj - - (loop (print (eval (read)))) - -Rather then write it like that, we can write it as follows: - -.. code-block:: clj - - (-> (read) (eval) (print) (loop)) - -Now, using `python-sh `_, we can show -how the threading macro (because of python-sh's setup) can be used like -a pipe: - -.. code-block:: clj - - => (import-from sh cat grep wc) - => (-> (cat "/usr/share/dict/words") (grep "-E" "^hy") (wc "-l")) - 210 - -Which, of course, expands out to: - -.. code-block:: clj - - (wc (grep (cat "/usr/share/dict/words") "-E" "^hy") "-l") - -Much more readable, no! Use the threading macro! - - - -TODO ----- - - - How do I define classes? - - Blow your mind with macros! - - Where's my banana??? + api + internals diff --git a/docs/language/internals.rst b/docs/language/internals.rst new file mode 100644 index 0000000..ccf8263 --- /dev/null +++ b/docs/language/internals.rst @@ -0,0 +1,27 @@ +========================= +Internal Hy Documentation +========================= + +.. info:: + These bits are for folks who hack on Hy it's self, mostly! + + +Hy Models +========= + +.. TODO:: + Write this. + + +Hy Macros +========= + +.. TODO:: + Write this. + + +Hy Compiler Builtins +==================== + +.. TODO:: + Write this. diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..78a84ef --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,38 @@ +========== +Quickstart +========== + +HOW TO GET HY REAL FAST: + +1. create a `Python virtual environment + `_ +2. activate your Python virtual environment +3. ``pip install hy`` +4. start a REPL with ``hy`` +5. type stuff in the REPL:: + + => (print "Hy!") + Hy! + => (defn salutationsnm [name] (print (+ "Hy " name "!"))) + => (salutationsnm "YourName") + Hy YourName! + + etc + +6. hit CTRL-D when you're done + +OMG! That's amazing! I want to write a hy program. + +7. open up an elite programming editor +8. type:: + + (print "i was going to code in python syntax, but then i got hy") + +9. save as ``test_program_of_awesome.hy`` +10. run:: + + hy test_program_of_awesome.hy + +11. take a deep breath so as to not hyperventilate +12. smile villainously and sneak off to your hydeaway and do + unspeakable things diff --git a/docs/tutorial.rst b/docs/tutorial.rst new file mode 100644 index 0000000..584652f --- /dev/null +++ b/docs/tutorial.rst @@ -0,0 +1,414 @@ +======== +Tutorial +======== + +Welcome to the Hy tutorial! + +In a nutshell, Hy is a lisp dialect, but one that converts its +structure into Python... literally a conversion into Python's abstract +syntax tree! (Or to put it in more crude terms, Hy is lisp-stick on a +python!) + +This is pretty cool because it means Hy is several things: + + - A lisp that feels very pythonic + - For lispers, a great way to use lisp's crazy powers but in the wide + world of Python's libraries (why yes, you now can write a Django + application in lisp!) + - For pythonistas, a great way to start exploring lisp, from the + comfort of python! + - For everyone: a pleasant language that has a lot of neat ideas! + + +Basic intro to lisp for pythonistas +=================================== + +Okay, maybe you've never used lisp before, but you've used python! + +A "hello world" in hy is actually super simple. Let's try it: + +.. code-block:: clj + + (print "hello world") + +See? Easy! As you may have guessed, this is the same as the python +version of:: + + print "hello world" + +To add up some super simple math, we could do: + +.. code-block:: clj + + (+ 1 3) + +Which would return 4 and would be the equivalent of: + +.. code-block:: clj + + 1 + 3 + +What you'll notice is that the first item in the list is the function +being called and the rest of the arguments are the arguments being +passed in. In fact, in hy (as with most lisps) we can pass in +multiple arguments to the plus operator: + +.. code-block:: clj + + (+ 1 3 55) + +Which would return 59. + +Maybe you've heard of lisp before but don't know much about it. Lisp +isn't as hard as you might think, and hy inherits from python, so hy +is a great way to start learning lisp. The main thing that's obvious +about lisp is that there's a lot of parentheses. This might seem +confusing at first, but it isn't so hard. Let's look at some simple +math that's wrapped in a bunch of parentheses that we could enter into +the hy interpreter: + +.. code-block:: clj + + (setv result (- (/ (+ 1 3 88) 2) 8)) + +This would return 37. But why? Well, we could look at the equivalent +expression in python:: + + result = ((1 + 3 + 88) / 2) - 8 + +If you were to try to figure out how the above were to work in python, +you'd of course figure out the results by solving each inner +parenthesis. That's the same basic idea in hy. Let's try this +exercise first in python:: + + result = ((1 + 3 + 88) / 2) - 8 + # simplified to... + result = (92 / 2) - 8 + # simplified to... + result = 46 - 8 + # simplified to... + result = 38 + +Now let's try the same thing in hy: + +.. code-block:: clj + + (setv result (- (/ (+ 1 3 88) 2) 8)) + ; simplified to... + (setv result (- (/ 92 2) 8)) + ; simplified to... + (setv result (- 46 8)) + ; simplified to... + (setv result 38) + +As you probably guessed, this last expression with "setv" means to +assign the variable "result" to 38. + +See? Not too hard! + +This is the basic premise of lisp... lisp stands for "list +processing"... this means that the structure of the program is +actually lists of lists. (If you're familiar with python lists, +imagine the entire same structure as above but with square brackets +instead, any you'll be able to see the structure above as both a +program and a datastructure.) This is easier to understand with more +examples, so let's write a simple python program and test it and then +show the equivalent hy program:: + + def simple_conversation(): + print "Hello! I'd like to get to know you. Tell me about yourself!" + name = raw_input("What is your name? ") + age = raw_input("What is your age? ") + print "Hello " + name + "! I see you are " + age + " years old." + + simple_conversation() + +If we ran this program, it might go like:: + + Hello! I'd like to get to know you. Tell me about yourself! + What is your name? Gary + What is your age? 38 + Hello Gary! I see you are 38 years old. + +Now let's look at the equivalent hy program: + +.. code-block:: clj + + (defn simple-conversation [] + (print "Hello! I'd like to get to know you. Tell me about yourself!") + (setv name (raw_input "What is your name? ")) + (setv age (raw_input "What is your age? ")) + (print (+ "Hello " name "! I see you are " + age " years old."))) + + (simple-conversation) + +If you look at the above program, as long as you remember that the +first element in each list of the program is the function (or +macro... we'll get to those later) being called and that the rest are +the arguments, it's pretty easy to figure out what this all means. +(As you probably also guessed, defn is the hy method of defining +methods.) + +Still, lots of people find this confusing at first because there's so +many parentheses, but there are plenty of things that can help make +this easier: keep indentation nice and use an editor with parenthesis +matching (this will help you figure out what each parenthesis pairs up +with) and things will start to feel comfortable. + +There are some advantages to having a code structure that's actually a +very simple datastructure as the core of lisp is based on. For one +thing, it means that your programs are easy to parse and that the +entire actual structure of the program is very clearly exposed to you. +(There's an extra step in hy where the structure you see is converted +to python's own representations... in more "pure" lisps such as common +lisp or emacs lisp, the data structure you see for the code and the +data structure that is executed is much more literally close.) + +Another implication of this is macros: if a program's structure is a +simple data structure, that means you can write code that can write +code very easily, meaning that implementing entirely new language +features can be very fast. Previous to hy, this wasn't very possible +for python programmers... now you too can make use of macros' +incredible power (just be careful to not aim them footward)! + + +Hy is python flavored lisp (or vice versa?) +=========================================== + +Hy converts to python's own abstract syntax tree, so you'll soon start +to find that all the familiar power of python is at your fingertips. + +You have full access to python's data types and standard library in +hy. Let's experiment with this in the hy interpreter:: + + => [1 2 3] + [1, 2, 3] + => {"dog" "bark" + ... "cat" "meow"} + ... + {'dog': 'bark', 'cat': 'meow'} + +(You may notice that at present, the common lisp method of quoting +things like so: + +.. code-block:: clj + + '(1 2 3) + +does not work. Instead, use square brackets as above.) + +You also have access to all the builtin types' nice methods:: + + => (.strip " fooooo ") + "fooooo" + +What's this? Yes indeed, this is precisely the same as:: + + " fooooo ".strip() + +That's right... lisp with dot notation! If we have this string +assigned as a variable, we can also do the following: + +.. code-block:: clj + + (setv this-string " fooooo ") + (this-string.strip) + +What about conditionals?: + +.. code-block:: clj + + (if (try-some-thing) + (print "this is if true") + (print "this is if false")) + +As you can tell above, the first argument to if is a truth test, the +second argument is a body if true, and the third argument (optional!) +is if false (ie, "else"!). + +If you need to do more complex conditionals, you'll find that you +don't have elif available in hy. Instead, you should use something +called "cond". In python, you might do something like:: + + somevar = 33 + if somevar > 50: + print "That variable is too big!" + elif somevar < 10: + print "That variable is too small!" + else: + print "That variable is jussssst right!" + +In hy, you would do: + +.. code-block:: clj + + (cond + ((> somevar 50) + (print "That variable is too big!")) + ((< somevar 10) + (print "That variable is too small!")) + (true + (print "That variable is jussssst right!"))) + +What you'll notice is that cond switches off between a some 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 +for "true"... that's because true will always be true, so if we get +this far, we'll always run that one! + +You might notice above that if you have code like: + +.. code-block:: clj + + (if some-condition + (body-if-true) + (body-if-false)) + +But wait! What if you want to execute more than one statment in the +body of one of these? + +You can do the following: + +.. code-block:: clj + + (if (try-some-thing) + (do + (print "this is if true") + (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 statments. If you're +familiar with other lisps, this is the equivalent of "progn" +elsewhere. + +Comments start with semicolons: + +.. code-block:: clj + + (print "this will run") + ; (print "but this will not") + (+ 1 2 3) ; we'll execute the addition, but not this comment! + +Looping is not hard but has a kind of special structure. In python, +we might do:: + + for i in range(10): + print "'i' is now at " + str(i) + +The equivalent in hy would be: + +.. code-block:: clj + + (for (i (range 10)) + (print (+ "'i' is now at " (str i)))) + + +You can also import and make use of various python libraries. For +example: + +.. code-block:: clj + + (import os) + + (if (os.path.isdir "/tmp/somedir") + (os.mkdir "/tmp/somedir/anotherdir") + (print "Hey, that path isn't there!")) + +Comments start with semicolons: + +.. code-block:: clj + + (print "this will run") + ; (print "but this will not") + (+ 1 2 3) ; we'll execute the addition, but not this comment! + +And yes, we do have lisp comprehensions! In Python you might do:: + + odds_squared = [ + pow(num, 2) + for num in range(100) + if num % 2 == 1] + +In hy, you could do these like: + +.. code-block:: clj + + (setv odds-squared + (list-comp + (pow num 2) + (num (range 100)) + (= (% num 2) 1))) + + +.. code-block:: clj + + ; And, an example stolen shamelessly from a Clojure page: + ; Let's list all the blocks of a Chessboard: + + (list-comp + (, x y) + (x (range 9) + y "ABCDEFGH")) + + ; [(0, 'A'), (0, 'B'), (0, 'C'), (0, 'D'), (0, 'E'), (0, 'F'), (0, 'G'), (0, 'H'), + ; (1, 'A'), (1, 'B'), (1, 'C'), (1, 'D'), (1, 'E'), (1, 'F'), (1, 'G'), (1, 'H'), + ; (2, 'A'), (2, 'B'), (2, 'C'), (2, 'D'), (2, 'E'), (2, 'F'), (2, 'G'), (2, 'H'), + ; (3, 'A'), (3, 'B'), (3, 'C'), (3, 'D'), (3, 'E'), (3, 'F'), (3, 'G'), (3, 'H'), + ; (4, 'A'), (4, 'B'), (4, 'C'), (4, 'D'), (4, 'E'), (4, 'F'), (4, 'G'), (4, 'H'), + ; (5, 'A'), (5, 'B'), (5, 'C'), (5, 'D'), (5, 'E'), (5, 'F'), (5, 'G'), (5, 'H'), + ; (6, 'A'), (6, 'B'), (6, 'C'), (6, 'D'), (6, 'E'), (6, 'F'), (6, 'G'), (6, 'H'), + ; (7, 'A'), (7, 'B'), (7, 'C'), (7, 'D'), (7, 'E'), (7, 'F'), (7, 'G'), (7, 'H'), + ; (8, 'A'), (8, 'B'), (8, 'C'), (8, 'D'), (8, 'E'), (8, 'F'), (8, 'G'), (8, 'H')] + + + +Protips! +======== + +Hy also features something known as the "threading macro", a really neat +feature of Clojure's. The "threading macro" (written as "->"), is used +to avoid deep nesting of expressions. + +The threading macro inserts each expression into the next expression's first +argument place. + +Let's take the classic: + +.. code-block:: clj + + (loop (print (eval (read)))) + +Rather then write it like that, we can write it as follows: + +.. code-block:: clj + + (-> (read) (eval) (print) (loop)) + +Now, using `python-sh `_, we can show +how the threading macro (because of python-sh's setup) can be used like +a pipe: + +.. code-block:: clj + + => (import-from sh cat grep wc) + => (-> (cat "/usr/share/dict/words") (grep "-E" "^hy") (wc "-l")) + 210 + +Which, of course, expands out to: + +.. code-block:: clj + + (wc (grep (cat "/usr/share/dict/words") "-E" "^hy") "-l") + +Much more readable, no! Use the threading macro! + + +TODO +==== + + - How do I define classes? + - Blow your mind with macros! + - Where's my banana??? + - Mention that you can import .hy files in .py files and vice versa! diff --git a/eg/python3/futures/hello-world.hy b/eg/python3/futures/hello-world.hy new file mode 100644 index 0000000..3b031bc --- /dev/null +++ b/eg/python3/futures/hello-world.hy @@ -0,0 +1,12 @@ +(import-from concurrent.futures ThreadPoolExecutor as-completed) +(import-from random randint) + +(import-from sh sleep) + +(defn task-to-do [] (sleep (randint 1 5))) + + +(with-as (ThreadPoolExecutor 10) executor + (setf jobs (list-comp (.submit executor task-to-do) (x (range 0 10)))) + (for (future (as-completed jobs)) + (.result future))) diff --git a/hy/__init__.py b/hy/__init__.py index 05ae409..f25dd2f 100644 --- a/hy/__init__.py +++ b/hy/__init__.py @@ -20,7 +20,7 @@ __appname__ = "hy" -__version__ = "0.9.3" +__version__ = "0.9.5" import hy.importer # NOQA diff --git a/hy/compiler.py b/hy/compiler.py index e1c7e56..7161f85 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1,4 +1,7 @@ +# -*- encoding: utf-8 -*- +# # Copyright (c) 2013 Paul Tagliamonte +# Copyright (c) 2013 Julien Danjou # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -28,17 +31,48 @@ from hy.models.symbol import HySymbol from hy.models.list import HyList from hy.models.dict import HyDict +from hy.util import flatten_literal_list + +import codecs import ast import sys class HyCompileError(HyError): - pass + def __init__(self, exception, + start_line=0, start_column=0): + self.exception = exception + self.start_line = start_line + self.start_column = start_column + + def __str__(self): + if self.start_line == 0: + return("Internal Compiler Bug\n⤷ %s: %s" + % (self.exception.__class__.__name__, + self.exception)) + return ("Compilation error at line %d, column %d\n%s: %s" + % (self.start_line, self.start_column, + self.exception.__class__.__name__, + self.exception)) _compile_table = {} +def ast_str(foobar): + if sys.version_info[0] >= 3: + return str(foobar) + + try: + return str(foobar) + except UnicodeEncodeError: + pass + + enc = codecs.getencoder('punycode') + foobar, _ = enc(foobar) + return "__hy_%s" % (str(foobar).replace("-", "_")) + + def builds(_type): def _dec(fn): _compile_table[_type] = fn @@ -49,6 +83,38 @@ def builds(_type): return _dec +def _raise_wrong_args_number(expression, error): + err = TypeError(error % (expression.pop(0), + len(expression))) + err.start_line = expression.start_line + err.start_column = expression.start_column + raise err + + +def checkargs(exact=None, min=None, max=None): + def _dec(fn): + def checker(self, expression): + if exact is not None and (len(expression) - 1) != exact: + _raise_wrong_args_number(expression, + "`%%s' needs %d arguments, got %%d" % + exact) + + if min is not None and (len(expression) - 1) < min: + _raise_wrong_args_number( + expression, + "`%%s' needs at least %d arguments, got %%d" % (min)) + + if max is not None and (len(expression) - 1) > max: + _raise_wrong_args_number( + expression, + "`%%s' needs at most %d arguments, got %%d" % (max)) + + return fn(self, expression) + + return checker + return _dec + + class HyASTCompiler(object): def __init__(self): @@ -56,14 +122,30 @@ class HyASTCompiler(object): self.anon_fn_count = 0 def compile(self, tree): - for _type in _compile_table: - if type(tree) == _type: - return _compile_table[_type](self, tree) + try: + for _type in _compile_table: + if type(tree) == _type: + return _compile_table[_type](self, tree) + except HyCompileError: + # compile calls compile, so we're going to have multiple raise + # nested; so let's re-raise this exception, let's not wrap it in + # another HyCompileError! + raise + except Exception as e: + raise HyCompileError(exception=e, + start_line=getattr(e, "start_line", 0), + start_column=getattr(e, "start_column", 0)) raise HyCompileError("Unknown type - `%s'" % (str(type(tree)))) - def _mangle_branch(self, tree): + def _mangle_branch(self, tree, start_line, start_column): + # If tree is empty, just return a pass statement + if tree == []: + return [ast.Pass(lineno=start_line, + col_offset=start_column)] + ret = [] + tree = list(flatten_literal_list(tree)) tree.reverse() if self.returnable and len(tree) > 0: @@ -73,10 +155,21 @@ class HyASTCompiler(object): ret.append(ast.Return(value=el, lineno=el.lineno, col_offset=el.col_offset)) - ret += [ast.Expr(value=el, - lineno=el.lineno, - col_offset=el.col_offset) - if not isinstance(el, ast.stmt) else el for el in tree] # NOQA + if isinstance(el, ast.FunctionDef): + ret.append(ast.Return( + value=ast.Name( + arg=el.name, id=el.name, ctx=ast.Load(), + lineno=el.lineno, col_offset=el.col_offset), + lineno=el.lineno, col_offset=el.col_offset)) + + for el in tree: + if isinstance(el, ast.stmt): + ret.append(el) + continue + + ret.append(ast.Expr(value=el, + lineno=el.lineno, + col_offset=el.col_offset)) ret.reverse() return ret @@ -95,9 +188,11 @@ class HyASTCompiler(object): return [self.compile(x) for x in expr[1:]] @builds("throw") + @builds("raise") + @checkargs(max=1) def compile_throw_expression(self, expr): expr.pop(0) - exc = self.compile(expr.pop(0)) + exc = self.compile(expr.pop(0)) if expr else None return ast.Raise( lineno=expr.start_line, col_offset=expr.start_column, @@ -116,52 +211,152 @@ class HyASTCompiler(object): else: Try = ast.TryExcept + try: + body = expr.pop(0) + except IndexError: + body = [] + + # (try something…) + body = self._code_branch(self.compile(body), + expr.start_line, + expr.start_column) + + orelse = [] + if len(expr) == 0: + # (try) or (try body) + handlers = [ast.ExceptHandler( + lineno=expr.start_line, + col_offset=expr.start_column, + type=None, + name=None, + body=[ast.Pass(lineno=expr.start_line, + col_offset=expr.start_column)])] + else: + handlers = [] + for e in expr: + if not len(e): + raise TypeError("Empty list not allowed in `try'") + + if e[0] in (HySymbol("except"), HySymbol("catch")): + handlers.append(self.compile(e)) + elif e[0] == HySymbol("else"): + if orelse: + raise TypeError( + "`try' cannot have more than one `else'") + else: + orelse = self._code_branch(self.compile(e[1:]), + e.start_line, + e.start_column) + else: + raise TypeError("Unknown expression in `try'") + + if handlers == []: + raise TypeError( + "`try' must have at least `except' or `finally'") + return Try( lineno=expr.start_line, col_offset=expr.start_column, - body=self._code_branch(self.compile(expr.pop(0))), - handlers=[self.compile(s) for s in expr], + body=body, + handlers=handlers, finalbody=[], - orelse=[]) + orelse=orelse) @builds("catch") + @builds("except") def compile_catch_expression(self, expr): - expr.pop(0) # catch - _type = self.compile(expr.pop(0)) - name = expr.pop(0) + catch = expr.pop(0) # catch - if sys.version_info[0] >= 3: - # Python3 features a change where the Exception handler - # moved the name from a Name() to a pure Python String type. - # - # We'll just make sure it's a pure "string", and let it work - # it's magic. - name = str(name) + try: + exceptions = expr.pop(0) + except IndexError: + exceptions = HyList() + # exceptions catch should be either: + # [[list of exceptions]] + # or + # [variable [list of exceptions]] + # or + # [variable exception] + # or + # [exception] + # or + # [] + if not isinstance(exceptions, HyList): + raise TypeError("`%s' exceptions list is not a list" % catch) + if len(exceptions) > 2: + raise TypeError("`%s' exceptions list is too long" % catch) + + # [variable [list of exceptions]] + # let's pop variable and use it as name + if len(exceptions) == 2: + name = exceptions.pop(0) + if sys.version_info[0] >= 3: + # Python3 features a change where the Exception handler + # moved the name from a Name() to a pure Python String type. + # + # We'll just make sure it's a pure "string", and let it work + # it's magic. + name = ast_str(name) + else: + # Python2 requires an ast.Name, set to ctx Store. + name = self._storeize(self.compile(name)) else: - # Python2 requires an ast.Name, set to ctx Store. - name = self.compile(name) - name.ctx = ast.Store() + name = None + + try: + exceptions_list = exceptions.pop(0) + except IndexError: + exceptions_list = [] + + if isinstance(exceptions_list, list): + if len(exceptions_list): + # [FooBar BarFoo] → catch Foobar and BarFoo exceptions + _type = ast.Tuple(elts=[self.compile(x) + for x in exceptions_list], + lineno=expr.start_line, + col_offset=expr.start_column, + ctx=ast.Load()) + else: + # [] → all exceptions catched + _type = None + elif isinstance(exceptions_list, HySymbol): + _type = self.compile(exceptions_list) + else: + raise TypeError("`%s' needs a valid exception list" % catch) + + body = self._code_branch([self.compile(x) for x in expr], + expr.start_line, + expr.start_column) return ast.ExceptHandler( lineno=expr.start_line, col_offset=expr.start_column, type=_type, name=name, - body=self._code_branch([self.compile(x) for x in expr])) + body=body) - def _code_branch(self, branch): - if isinstance(branch, list): - return self._mangle_branch(branch) - return self._mangle_branch([branch]) + def _code_branch(self, branch, start_line, start_column): + return self._mangle_branch((branch + if isinstance(branch, list) + else [branch]), + start_line, + start_column) @builds("if") + @checkargs(min=2, max=3) def compile_if_expression(self, expr): - expr.pop(0) + expr.pop(0) # if test = self.compile(expr.pop(0)) - body = self._code_branch(self.compile(expr.pop(0))) - orel = [] - if len(expr) > 0: - orel = self._code_branch(self.compile(expr.pop(0))) + body = self._code_branch(self.compile(expr.pop(0)), + expr.start_line, + expr.start_column) + + if len(expr) == 1: + orel = self._code_branch(self.compile(expr.pop(0)), + expr.start_line, + expr.start_column) + else: + orel = [] return ast.If(test=test, body=body, @@ -190,6 +385,7 @@ class HyASTCompiler(object): nl=True) @builds("assert") + @checkargs(1) def compile_assert_expression(self, expr): expr.pop(0) # assert e = expr.pop(0) @@ -199,6 +395,7 @@ class HyASTCompiler(object): col_offset=e.start_column) @builds("lambda") + @checkargs(min=2) def compile_lambda_expression(self, expr): expr.pop(0) sig = expr.pop(0) @@ -208,7 +405,7 @@ class HyASTCompiler(object): lineno=expr.start_line, col_offset=expr.start_column, args=ast.arguments(args=[ - ast.Name(arg=str(x), id=str(x), + ast.Name(arg=ast_str(x), id=ast_str(x), ctx=ast.Param(), lineno=x.start_line, col_offset=x.start_column) @@ -221,10 +418,12 @@ class HyASTCompiler(object): body=self.compile(body)) @builds("pass") + @checkargs(0) def compile_pass_expression(self, expr): return ast.Pass(lineno=expr.start_line, col_offset=expr.start_column) @builds("yield") + @checkargs(1) def compile_yield_expression(self, expr): expr.pop(0) return ast.Yield( @@ -238,7 +437,7 @@ class HyASTCompiler(object): return ast.Import( lineno=expr.start_line, col_offset=expr.start_column, - names=[ast.alias(name=str(x), asname=None) for x in expr]) + names=[ast.alias(name=ast_str(x), asname=None) for x in expr]) @builds("import_as") def compile_import_as_expression(self, expr): @@ -247,21 +446,23 @@ class HyASTCompiler(object): return ast.Import( lineno=expr.start_line, col_offset=expr.start_column, - module=str(expr.pop(0)), - names=[ast.alias(name=str(x[0]), - asname=str(x[1])) for x in modlist]) + module=ast_str(expr.pop(0)), + names=[ast.alias(name=ast_str(x[0]), + asname=ast_str(x[1])) for x in modlist]) @builds("import_from") + @checkargs(min=1) def compile_import_from_expression(self, expr): expr.pop(0) # index return ast.ImportFrom( lineno=expr.start_line, col_offset=expr.start_column, - module=str(expr.pop(0)), - names=[ast.alias(name=str(x), asname=None) for x in expr], + module=ast_str(expr.pop(0)), + names=[ast.alias(name=ast_str(x), asname=None) for x in expr], level=0) @builds("get") + @checkargs(2) def compile_index_expression(self, expr): expr.pop(0) # index val = self.compile(expr.pop(0)) # target @@ -275,6 +476,7 @@ class HyASTCompiler(object): ctx=ast.Load()) @builds("slice") + @checkargs(min=1, max=3) def compile_slice_expression(self, expr): expr.pop(0) # index val = self.compile(expr.pop(0)) # target @@ -297,6 +499,7 @@ class HyASTCompiler(object): ctx=ast.Load()) @builds("assoc") + @checkargs(3) def compile_assoc_expression(self, expr): expr.pop(0) # assoc # (assoc foo bar baz) => foo[bar] = baz @@ -317,6 +520,7 @@ class HyASTCompiler(object): value=self.compile(val)) @builds("decorate_with") + @checkargs(min=1) def compile_decorate_expression(self, expr): expr.pop(0) # decorate-with fn = self.compile(expr.pop(-1)) @@ -325,23 +529,30 @@ class HyASTCompiler(object): fn.decorator_list = [self.compile(x) for x in expr] return fn - @builds("with_as") - def compile_with_as_expression(self, expr): - expr.pop(0) # with-as - ctx = self.compile(expr.pop(0)) - thing = self.compile(expr.pop(0)) - if isinstance(thing, ast.Tuple): - for x in thing.elts: - x.ctx = ast.Store() + @builds("with") + @checkargs(min=2) + def compile_with_expression(self, expr): + expr.pop(0) # with - thing.ctx = ast.Store() + args = expr.pop(0) + if len(args) > 2 or len(args) < 1: + raise TypeError("with needs [arg (expr)] or [(expr)]") + + args.reverse() + ctx = self.compile(args.pop(0)) + + thing = None + if args != []: + thing = self._storeize(self.compile(args.pop(0))) ret = ast.With(context_expr=ctx, lineno=expr.start_line, col_offset=expr.start_column, optional_vars=thing, - body=self._mangle_branch([ - self.compile(x) for x in expr])) + body=self._code_branch( + [self.compile(x) for x in expr], + expr.start_line, + expr.start_column)) if sys.version_info[0] >= 3 and sys.version_info[1] >= 3: ret.items = [ast.withitem(context_expr=ctx, optional_vars=thing)] @@ -357,32 +568,42 @@ class HyASTCompiler(object): ctx=ast.Load()) @builds("list_comp") + @checkargs(min=2, max=3) def compile_list_comprehension(self, expr): - # (list-comp expr (target iter)) + # (list-comp expr (target iter) cond?) expr.pop(0) - thing = self.compile(expr.pop(0)) - ident, gen = expr.pop(0) + expression = expr.pop(0) + tar_it = iter(expr.pop(0)) + targets = zip(tar_it, tar_it) - ident = self.compile(ident) - gen = self.compile(gen) + cond = self.compile(expr.pop(0)) if expr != [] else None - if isinstance(ident, ast.Tuple): - for x in ident.elts: - x.ctx = ast.Store() - ident.ctx = ast.Store() - - return ast.ListComp( + ret = ast.ListComp( lineno=expr.start_line, col_offset=expr.start_column, - elt=thing, - generators=[ - ast.comprehension( - target=ident, - iter=gen, - ifs=[self.compile(x) for x in expr]) - ]) + elt=self.compile(expression), + generators=[]) + + for target, iterable in targets: + ret.generators.append(ast.comprehension( + target=self._storeize(self.compile(target)), + iter=self.compile(iterable), + ifs=[])) + + if cond: + ret.generators[-1].ifs.append(cond) + + return ret + + def _storeize(self, name): + if isinstance(name, ast.Tuple): + for x in name.elts: + x.ctx = ast.Store() + name.ctx = ast.Store() + return name @builds("kwapply") + @checkargs(2) def compile_kwapply_expression(self, expr): expr.pop(0) # kwapply call = self.compile(expr.pop(0)) @@ -391,11 +612,39 @@ class HyASTCompiler(object): if type(call) != ast.Call: raise TypeError("kwapplying a non-call") - call.keywords = [ast.keyword(arg=str(x), + call.keywords = [ast.keyword(arg=ast_str(x), value=self.compile(kwargs[x])) for x in kwargs] return call + @builds("not") + @builds("~") + @checkargs(1) + def compile_unary_operator(self, expression): + ops = {"not": ast.Not, + "~": ast.Invert} + operator = expression.pop(0) + operand = expression.pop(0) + return ast.UnaryOp(op=ops[operator](), + operand=self.compile(operand), + lineno=operator.start_line, + col_offset=operator.start_column) + + @builds("and") + @builds("or") + @checkargs(min=2) + def compile_logical_or_and_and_operator(self, expression): + ops = {"and": ast.And, + "or": ast.Or} + operator = expression.pop(0) + values = [] + for child in expression: + values.append(self.compile(child)) + return ast.BoolOp(op=ops[operator](), + lineno=operator.start_line, + col_offset=operator.start_column, + values=values) + @builds("=") @builds("!=") @builds("<") @@ -406,6 +655,7 @@ class HyASTCompiler(object): @builds("in") @builds("is_not") @builds("not_in") + @checkargs(min=2) def compile_compare_op_expression(self, expression): ops = {"=": ast.Eq, "!=": ast.NotEq, "<": ast.Lt, "<=": ast.LtE, @@ -429,6 +679,7 @@ class HyASTCompiler(object): @builds("-") @builds("/") @builds("*") + @checkargs(min=2) def compile_maths_expression(self, expression): # operator = Mod | Pow | LShift | RShift | BitOr | # BitXor | BitAnd | FloorDiv @@ -467,7 +718,7 @@ class HyASTCompiler(object): lineno=expr.start_line, col_offset=expr.start_column, value=self.compile(obj), - attr=str(fn), + attr=ast_str(fn), ctx=ast.Load()), args=[self.compile(x) for x in expr], keywords=[], @@ -499,6 +750,7 @@ class HyASTCompiler(object): @builds("def") @builds("setf") @builds("setv") + @checkargs(2) def compile_def_expression(self, expression): expression.pop(0) # "def" name = expression.pop(0) @@ -509,15 +761,10 @@ class HyASTCompiler(object): # We special case a FunctionDef, since we can define by setting # FunctionDef's .name attribute, rather then foo == anon_fn. This # helps keep things clean. - what.name = str(name) + what.name = ast_str(name) return what - name = self.compile(name) - if isinstance(name, ast.Tuple): - for x in name.elts: - x.ctx = ast.Store() - - name.ctx = ast.Store() + name = self._storeize(self.compile(name)) return ast.Assign( lineno=expression.start_line, @@ -525,34 +772,43 @@ class HyASTCompiler(object): targets=[name], value=what) @builds("foreach") + @checkargs(min=1) def compile_for_expression(self, expression): ret_status = self.returnable self.returnable = False expression.pop(0) # for name, iterable = expression.pop(0) - target = self.compile_symbol(name) - - if isinstance(target, ast.Tuple): - for x in target.elts: - x.ctx = ast.Store() - - target.ctx = ast.Store() - # support stuff like: - # (for [x [1 2 3 4] - # y [a b c d]] ...) + target = self._storeize(self.compile_symbol(name)) ret = ast.For(lineno=expression.start_line, col_offset=expression.start_column, target=target, iter=self.compile(iterable), - body=self._mangle_branch([ - self.compile(x) for x in expression]), + body=self._code_branch( + [self.compile(x) for x in expression], + expression.start_line, + expression.start_column), orelse=[]) self.returnable = ret_status return ret + @builds("while") + @checkargs(min=2) + def compile_while_expression(self, expr): + expr.pop(0) # "while" + test = self.compile(expr.pop(0)) + + return ast.While(test=test, + body=self._code_branch( + [self.compile(x) for x in expr], + expr.start_line, + expr.start_column), + orelse=[], + lineno=expr.start_line, + col_offset=expr.start_column) + @builds(HyList) def compile_list(self, expr): return ast.List( @@ -562,16 +818,30 @@ class HyASTCompiler(object): col_offset=expr.start_column) @builds("fn") + @checkargs(min=2) def compile_fn_expression(self, expression): expression.pop(0) # fn ret_status = self.returnable - self.returnable = True self.anon_fn_count += 1 name = "_hy_anon_fn_%d" % (self.anon_fn_count) sig = expression.pop(0) + body = [] + if expression != []: + self.returnable = True + tailop = self.compile(expression.pop(-1)) + self.returnable = False + for el in expression: + body.append(self.compile(el)) + body.append(tailop) + + self.returnable = True + body = self._code_branch(body, + expression.start_line, + expression.start_column) + ret = ast.FunctionDef( name=name, lineno=expression.start_line, @@ -579,7 +849,7 @@ class HyASTCompiler(object): args=ast.arguments( args=[ ast.Name( - arg=str(x), id=str(x), + arg=ast_str(x), id=ast_str(x), ctx=ast.Param(), lineno=x.start_line, col_offset=x.start_column) @@ -589,8 +859,7 @@ class HyASTCompiler(object): kwonlyargs=[], kw_defaults=[], defaults=[]), - body=self._code_branch([ - self.compile(x) for x in expression]), + body=body, decorator_list=[]) self.returnable = ret_status @@ -613,19 +882,19 @@ class HyASTCompiler(object): lineno=symbol.start_line, col_offset=symbol.start_column, value=self.compile_symbol(glob), - attr=str(local), + attr=ast_str(local), ctx=ast.Load() ) - return ast.Name(id=str(symbol), - arg=str(symbol), + return ast.Name(id=ast_str(symbol), + arg=ast_str(symbol), ctx=ast.Load(), lineno=symbol.start_line, col_offset=symbol.start_column) @builds(HyString) def compile_string(self, string): - return ast.Str(s=str(string), lineno=string.start_line, + return ast.Str(s=ast_str(string), lineno=string.start_line, col_offset=string.start_column) @builds(HyDict) @@ -649,5 +918,5 @@ def hy_compile(tree, root=None): tlo = root if root is None: tlo = ast.Module - ret = tlo(body=compiler._mangle_branch(compiler.compile(tree))) + ret = tlo(body=compiler._mangle_branch(compiler.compile(tree), 0, 0)) return ret diff --git a/hy/core/__init__.py b/hy/core/__init__.py index ac5262a..65be1af 100644 --- a/hy/core/__init__.py +++ b/hy/core/__init__.py @@ -20,15 +20,24 @@ from hy.macros import process as mprocess +import hy.mangle + MACROS = [ - "hy.core.bootstrap", # defn, cond + "hy.core.bootstrap", + "hy.core.mangles", ] def process(tree): load_macros() - return mprocess(tree) + old = None + while old != tree: + old = tree + tree = mprocess(tree) + for m in hy.mangle.MANGLES: + m().mangle(tree) + return tree def load_macros(): diff --git a/hy/core/bootstrap.py b/hy/core/bootstrap.py index 9d46289..50efc66 100644 --- a/hy/core/bootstrap.py +++ b/hy/core/bootstrap.py @@ -94,6 +94,20 @@ def threading_macro(tree): return ret +@macro("_>>") +def threading_tail_macro(tree): + tree.pop(0) + ret = tree.pop(0) + for node in tree: + if not isinstance(node, HyExpression): + nnode = HyExpression([node]) + nnode.replace(node) + node = nnode + node.append(ret) + ret = node + return ret + + @macro("car") @macro("first") def first_macro(tree): @@ -114,3 +128,19 @@ def rest_macro(tree): return HyExpression([HySymbol('slice'), ret, HyInteger(1)]) + + +@macro("let") +def let_macro(tree): + tree.pop(0) # "let" + ret = tree.pop(0) # vars + # tree is now the body + expr = HyExpression([HySymbol("fn"), HyList([])]) + + for var in ret: + expr.append(HyExpression([HySymbol("setf"), var[0], var[1]])) + + for stmt in tree: + expr.append(stmt) + + return HyExpression([expr]) diff --git a/hy/core/mangles.py b/hy/core/mangles.py new file mode 100644 index 0000000..4aaf210 --- /dev/null +++ b/hy/core/mangles.py @@ -0,0 +1,85 @@ +# 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. + +from hy.models.expression import HyExpression +from hy.models.symbol import HySymbol +from hy.models.list import HyList + +import hy.mangle + + +class HoistableMangle(hy.mangle.Mangle): + def should_hoist(self): + for frame in self.stack: + if frame is self.scope: + return False + + if isinstance(frame, HyExpression) and frame != []: + call = frame[0] + if call in self.ignore: + continue + return True + return False + + +class FunctionMangle(HoistableMangle): + hoistable = ["fn"] + ignore = ["def", "decorate_with", "setf", "setv"] + + def __init__(self): + self.series = 0 + + def unique_name(self): + self.series += 1 + return "_hy_hoisted_fn_%s" % (self.series) + + def visit(self, tree): + if isinstance(tree, HyExpression) and tree != []: + call = tree[0] + if call == "fn" and self.should_hoist(): + new_name = HySymbol(self.unique_name()) + new_name.replace(tree) + fn_def = HyExpression([HySymbol("def"), + new_name, + tree]) + fn_def.replace(tree) + self.hoist(fn_def) + return new_name + + +class IfMangle(HoistableMangle): + ignore = [] + + def __init__(self): + self.series = 0 + + def visit(self, tree): + if isinstance(tree, HyExpression) and tree != []: + call = tree[0] + if call == "if" and self.should_hoist(): + fn = HyExpression([HyExpression([HySymbol("fn"), + HyList([]), + tree])]) + fn.replace(tree) + return fn + + +hy.mangle.MANGLES.append(IfMangle) +hy.mangle.MANGLES.append(FunctionMangle) diff --git a/hy/importer.py b/hy/importer.py index 1569d38..587d536 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -19,11 +19,12 @@ # DEALINGS IN THE SOFTWARE. from hy.compiler import hy_compile -from hy.lex import tokenize -from hy.core import process from py_compile import wr_long, MAGIC +from hy.core import process +from hy.lex import tokenize +from io import open import marshal import imp import sys @@ -43,7 +44,7 @@ def import_buffer_to_hst(fd): def import_file_to_hst(fpath): - return import_buffer_to_hst(open(fpath, 'r')) + return import_buffer_to_hst(open(fpath, 'r', encoding='utf-8')) def import_file_to_ast(fpath): diff --git a/hy/lex/states.py b/hy/lex/states.py index 8e5951c..721c6c0 100644 --- a/hy/lex/states.py +++ b/hy/lex/states.py @@ -166,6 +166,9 @@ class ListeyThing(State): if char == self.end_char: return Idle + if char in ")]}": + raise LexException("Unexpected closing character: `%s'" % (char)) + if char in WHITESPACE: self.commit() return @@ -241,6 +244,9 @@ class String(State): if char == "\\": self.nodes.append("\\") return + if char == "\"": + self.nodes.append("\"") + return raise LexException("Unknown modifier: `%s'" % (char)) diff --git a/hy/mangle.py b/hy/mangle.py new file mode 100644 index 0000000..a0f2051 --- /dev/null +++ b/hy/mangle.py @@ -0,0 +1,109 @@ +# 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. + +from hy.models.expression import HyExpression +# from hy.models.list import HyList + +MANGLES = [] + + +class Mangle(object): + """ + Mangle (n.) + + 1. To mutilate or disfigure by battering, hacking, cutting, + or tearing. See Synonyms at batter1. + + (but mostly hacking) + """ + + class TreeChanged(Exception): + pass + + def _mangle(self, tree): + # Things that force a scope push to go into: + # + # - Functions + # - If + scopable = ["fn", "if"] + scoped = False + + self.push_stack(tree) + + if isinstance(tree, HyExpression): + what = tree[0] + if what in scopable: + self.push_scope(tree) + scoped = True + + if isinstance(tree, list): + for i, element in enumerate(tree): + nel = self.visit(element) + if nel: + tree[i] = nel + self.tree_changed() + + self._mangle(element) + + if scoped: + self.pop_scope() + self.pop_stack() + + def hoist(self, what): + scope = self.scope + for point, el in enumerate(scope): + if el in self.stack: + break + self.scope.insert(point, what) + + def get_scope(self): + return self.scopes[0] + + def tree_changed(self): + raise self.TreeChanged() + + @property + def scope(self): + return self.get_scope() + + def push_scope(self, tree): + self.scopes.insert(0, tree) + + def push_stack(self, tree): + self.stack.insert(0, tree) + + def pop_scope(self): + return self.scopes.pop(0) + + def pop_stack(self): + return self.stack.pop(0) + + def mangle(self, tree): + unfinished = True + while unfinished: + self.root = tree + self.scopes = [] + self.stack = [] + self.push_scope(tree) + try: + self._mangle(tree) + unfinished = False + except self.TreeChanged: + pass diff --git a/hy/models/string.py b/hy/models/string.py index ec64504..a700470 100644 --- a/hy/models/string.py +++ b/hy/models/string.py @@ -23,18 +23,15 @@ import sys if sys.version_info[0] >= 3: - _str_type = str + str_type = str else: - _str_type = unicode + str_type = unicode -class HyString(HyObject, _str_type): +class HyString(HyObject, str_type): """ Generic Hy String object. Helpful to store string literals from Hy scripts. It's either a ``str`` or a ``unicode``, depending on the Python version. """ - - def __new__(cls, value): - obj = _str_type.__new__(cls, value) - return obj + pass diff --git a/hy/util.py b/hy/util.py new file mode 100644 index 0000000..f2ffdf1 --- /dev/null +++ b/hy/util.py @@ -0,0 +1,28 @@ +# 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. + + +def flatten_literal_list(entry): + for e in entry: + if type(e) == list: + for x in flatten_literal_list(e): + yield x # needs more yield-from + else: + yield e diff --git a/site/app.hy b/site/app.hy index 82da14a..77ada57 100644 --- a/site/app.hy +++ b/site/app.hy @@ -28,6 +28,6 @@ (post-route hy2py "/hy2py" [] (try (hy-to-py (get request.form "code")) - (catch LexException e (err "Incomplete Code.")) - (catch HyError e (err "Generic error during processing.")) - (catch Exception e (err "Erm, you broke something.")))) + (catch [e LexException] (err "Incomplete Code.")) + (catch [e HyError] (err "Generic error during processing.")) + (catch [e Exception] (err "Erm, you broke something.")))) diff --git a/tests/__init__.py b/tests/__init__.py index 8b85ff7..efc173a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,6 @@ # -import hy -from .native_tests.math import * -from .native_tests.language import * +import hy # noqa +from .native_tests.math import * # noqa +from .native_tests.language import * # noqa diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index d3e9d51..d239a7f 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Paul Tagliamonte +# Copyright (c) 2013 Julien Danjou # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -34,6 +35,15 @@ def _ast_spotcheck(arg, root, secondary): assert getattr(root, arg) == getattr(secondary, arg) +def cant_compile(expr): + expr = tokenize(expr) + try: + hy_compile(expr) + assert False + except HyCompileError: + pass + + def test_ast_bad_type(): "Make sure AST breakage can happen" try: @@ -43,21 +53,239 @@ def test_ast_bad_type(): pass +def test_ast_bad_if(): + "Make sure AST can't compile invalid if" + cant_compile("(if)") + cant_compile("(if foobar)") + cant_compile("(if 1 2 3 4 5)") + + +def test_ast_valid_if(): + "Make sure AST can't compile invalid if" + hy_compile(tokenize("(if foo bar)")) + + +def test_ast_valid_unary_op(): + "Make sure AST can compile valid unary operator" + hy_compile(tokenize("(not 2)")) + hy_compile(tokenize("(~ 1)")) + + +def test_ast_invalid_unary_op(): + "Make sure AST can't compile invalid unary operator" + cant_compile("(not 2 3 4)") + cant_compile("(not)") + cant_compile("(not 2 3 4)") + cant_compile("(~ 2 2 3 4)") + cant_compile("(~)") + + +def test_ast_bad_while(): + "Make sure AST can't compile invalid while" + cant_compile("(while)") + cant_compile("(while (true))") + + +def test_ast_good_do(): + "Make sure AST can compile valid do" + hy_compile(tokenize("(do)")) + hy_compile(tokenize("(do 1)")) + + +def test_ast_good_throw(): + "Make sure AST can compile valid throw" + hy_compile(tokenize("(throw)")) + hy_compile(tokenize("(throw 1)")) + + +def test_ast_bad_throw(): + "Make sure AST can't compile invalid throw" + cant_compile("(raise 1 2 3)") + + +def test_ast_good_raise(): + "Make sure AST can compile valid raise" + hy_compile(tokenize("(raise)")) + hy_compile(tokenize("(raise 1)")) + + +def test_ast_bad_raise(): + "Make sure AST can't compile invalid raise" + cant_compile("(raise 1 2 3)") + + +def test_ast_good_try(): + "Make sure AST can compile valid try" + hy_compile(tokenize("(try)")) + hy_compile(tokenize("(try 1)")) + hy_compile(tokenize("(try 1 (except) (else 1))")) + hy_compile(tokenize("(try 1 (else 1) (except))")) + + +def test_ast_bad_try(): + "Make sure AST can't compile invalid try" + cant_compile("(try 1 bla)") + cant_compile("(try 1 bla bla)") + cant_compile("(try (do) (else 1) (else 2))") + cant_compile("(try 1 (else 1))") + + +def test_ast_good_catch(): + "Make sure AST can compile valid catch" + hy_compile(tokenize("(catch)")) + hy_compile(tokenize("(catch [])")) + hy_compile(tokenize("(catch [Foobar])")) + hy_compile(tokenize("(catch [[]])")) + hy_compile(tokenize("(catch [x FooBar])")) + hy_compile(tokenize("(catch [x [FooBar BarFoo]])")) + hy_compile(tokenize("(catch [x [FooBar BarFoo]])")) + + +def test_ast_bad_catch(): + "Make sure AST can't compile invalid catch" + cant_compile("(catch 1)") + cant_compile("(catch \"A\")") + cant_compile("(catch [1 3])") + cant_compile("(catch [x [FooBar] BarBar])") + + +def test_ast_good_except(): + "Make sure AST can compile valid except" + hy_compile(tokenize("(except)")) + hy_compile(tokenize("(except [])")) + hy_compile(tokenize("(except [Foobar])")) + hy_compile(tokenize("(except [[]])")) + hy_compile(tokenize("(except [x FooBar])")) + hy_compile(tokenize("(except [x [FooBar BarFoo]])")) + hy_compile(tokenize("(except [x [FooBar BarFoo]])")) + + +def test_ast_bad_except(): + "Make sure AST can't compile invalid except" + cant_compile("(except 1)") + cant_compile("(except [1 3])") + cant_compile("(except [x [FooBar] BarBar])") + + +def test_ast_good_assert(): + "Make sure AST can compile valid assert" + hy_compile(tokenize("(assert 1)")) + + +def test_ast_bad_assert(): + "Make sure AST can't compile invalid assert" + cant_compile("(assert)") + cant_compile("(assert 1 2)") + + +def test_ast_good_lambda(): + "Make sure AST can compile valid lambda" + hy_compile(tokenize("(lambda [] 1)")) + + +def test_ast_bad_lambda(): + "Make sure AST can't compile invalid lambda" + cant_compile("(lambda)") + cant_compile("(lambda [])") + + +def test_ast_good_pass(): + "Make sure AST can compile valid pass" + hy_compile(tokenize("(pass)")) + + +def test_ast_bad_pass(): + "Make sure AST can't compile invalid pass" + cant_compile("(pass 1)") + cant_compile("(pass 1 2)") + + +def test_ast_good_yield(): + "Make sure AST can compile valid yield" + hy_compile(tokenize("(yield 1)")) + + +def test_ast_bad_yield(): + "Make sure AST can't compile invalid yield" + cant_compile("(yield)") + cant_compile("(yield 1 2)") + + +def test_ast_good_import_from(): + "Make sure AST can compile valid import-from" + hy_compile(tokenize("(import-from x y)")) + + +def test_ast_bad_import_from(): + "Make sure AST can't compile invalid import-from" + cant_compile("(import-from)") + + +def test_ast_good_get(): + "Make sure AST can compile valid get" + hy_compile(tokenize("(get x y)")) + + +def test_ast_bad_get(): + "Make sure AST can't compile invalid get" + cant_compile("(get)") + cant_compile("(get 1)") + cant_compile("(get 1 2 3)") + + +def test_ast_good_slice(): + "Make sure AST can compile valid slice" + hy_compile(tokenize("(slice x)")) + hy_compile(tokenize("(slice x y)")) + hy_compile(tokenize("(slice x y z)")) + + +def test_ast_bad_slice(): + "Make sure AST can't compile invalid slice" + cant_compile("(slice)") + cant_compile("(slice 1 2 3 4)") + + +def test_ast_good_assoc(): + "Make sure AST can compile valid assoc" + hy_compile(tokenize("(assoc x y z)")) + + +def test_ast_bad_assoc(): + "Make sure AST can't compile invalid assoc" + cant_compile("(assoc)") + cant_compile("(assoc 1)") + cant_compile("(assoc 1 2)") + cant_compile("(assoc 1 2 3 4)") + + +def test_ast_bad_with(): + "Make sure AST can't compile invalid with" + cant_compile("(with)") + cant_compile("(with [])") + cant_compile("(with [] (pass))") + + +def test_ast_valid_while(): + "Make sure AST can't compile invalid while" + hy_compile(tokenize("(while foo bar)")) + + def test_ast_expression_basics(): """ Ensure basic AST expression conversion works. """ code = hy_compile(tokenize("(foo bar)")).body[0] tree = ast.Expr(value=ast.Call( - func=ast.Name( - id="foo", - ctx=ast.Load(), - ), - args=[ - ast.Name(id="bar", ctx=ast.Load()) - ], - keywords=[], - starargs=None, - kwargs=None, - )) + func=ast.Name( + id="foo", + ctx=ast.Load(), + ), + args=[ + ast.Name(id="bar", ctx=ast.Load()) + ], + keywords=[], + starargs=None, + kwargs=None, + )) _ast_spotcheck("value.func.id", code, tree) @@ -70,11 +298,7 @@ def test_ast_anon_fns_basics(): def test_ast_non_decoratable(): """ Ensure decorating garbage breaks """ - try: - hy_compile(tokenize("(decorate-with (foo) (* x x))")) - assert True is False - except TypeError: - pass + cant_compile("(decorate-with (foo) (* x x))") def test_ast_non_kwapplyable(): @@ -84,7 +308,7 @@ def test_ast_non_kwapplyable(): try: hy_compile(code) assert True is False - except TypeError: + except HyCompileError: pass diff --git a/tests/importer/test_importer.py b/tests/importer/test_importer.py index a63c071..ce5627c 100644 --- a/tests/importer/test_importer.py +++ b/tests/importer/test_importer.py @@ -4,8 +4,8 @@ import ast def test_basics(): "Make sure the basics of the importer work" - module = import_file_to_module("basic", - "tests/resources/importer/basic.hy") + import_file_to_module("basic", + "tests/resources/importer/basic.hy") def test_stringer(): diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index 5660418..a6a1193 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -33,13 +33,28 @@ from hy.lex import tokenize def test_lex_exception(): """ Ensure tokenize throws a fit on a partial input """ try: - objs = tokenize("(foo") + tokenize("(foo") assert True is False except LexException: pass try: - objs = tokenize("&foo&") + tokenize("&foo&") + assert True is False + except LexException: + pass + + +def test_unbalanced_exception(): + """Ensure the tokenization fails on unbalanced expressions""" + try: + tokenize("(bar))") + assert True is False + except LexException: + pass + + try: + tokenize("(baz [quux]])") assert True is False except LexException: pass diff --git a/tests/macros/test_macro_processor.py b/tests/macros/test_macro_processor.py index dd457d3..d9d7144 100644 --- a/tests/macros/test_macro_processor.py +++ b/tests/macros/test_macro_processor.py @@ -2,9 +2,7 @@ from hy.macros import macro, process from hy.lex import tokenize -from hy.models.expression import HyExpression from hy.models.string import HyString -from hy.models.symbol import HySymbol from hy.models.list import HyList diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 8c318d6..c835605 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -1,9 +1,9 @@ ; -(import-from tests.resources kwtest) +(import-from tests.resources kwtest function-with-a-dash) (import-from os.path exists isdir isfile) -(import sys) (import-as sys systest) +(import sys) (defn test-sys-argv [] @@ -32,6 +32,30 @@ (assert (= count 150))) +(defn test-while-loop [] + "NATIVE: test while loops?" + (setv count 5) + (setv fact 1) + (while (> count 0) + (setv fact (* fact count)) + (setv count (- count 1))) + (assert (= count 0)) + (assert (= fact 120))) + + +(defn test-not [] + "NATIVE: test not" + (assert (not (= 1 2))) + (assert (= true (not false))) + (assert (= false (not 42))) ) + + +(defn test-inv [] + "NATIVE: test inv" + (assert (= (~ 1) -2)) + (assert (= (~ -2) 1))) + + (defn test-in [] "NATIVE: test in" (assert (in "a" ["a" "b" "c" "d"])) @@ -74,6 +98,17 @@ (assert (= 1 1)) (assert (= 1 1))))) +(defn test-branching-expr-count-with-do [] + "NATIVE: make sure we execute the right number of expressions in the branch" + (setv counter 0) + (if false + (assert (= 2 1)) + (do + (setv counter (+ counter 1)) + (setv counter (+ counter 1)) + (setv counter (+ counter 1)))) + (assert (= counter 3))) + (defn test-cond [] "NATIVE: test if cond sorta works." @@ -84,7 +119,7 @@ (defn test-index [] "NATIVE: Test that dict access works" - (assert (get {"one" "two"} "one") "two") + (assert (= (get {"one" "two"} "one") "two")) (assert (= (get [1 2 3 4 5] 1) 2))) @@ -125,12 +160,134 @@ (assert (= (.join " " ["one" "two"]) "one two"))) +(defn test-do [] + "NATIVE: test do" + (do)) + + (defn test-exceptions [] "NATIVE: test Exceptions" + + (try) + + (try (do)) + + (try (pass)) + + (try (pass) (except)) + + (try (pass) (except [IOError]) (except)) + + ;; Test correct (raise) + (let [[passed false]] + (try + (try + (raise IndexError) + (except [IndexError] (raise))) + (except [IndexError] + (setv passed true))) + (assert passed)) + + ;; Test incorrect (raise) + (let [[passed false]] + (try + (raise) + ;; Python 2 raises TypeError + ;; Python 3 raises RuntimeError + (except [[TypeError RuntimeError]] + (setv passed true))) + (assert passed)) + + (try + (raise (KeyError)) + (catch [[IOError]] (assert false)) + (catch [e [KeyError]] (assert e))) + (try (throw (KeyError)) - (catch IOError e (assert (= 2 1))) - (catch KeyError e (+ 1 1) (assert (= 1 1))))) + (except [[IOError]] (assert false)) + (catch [e [KeyError]] (assert e))) + + + (try + (get [1] 3) + (catch [IndexError] (assert true)) + (except [IndexError] (pass))) + + (try + (print foobar42ofthebaz) + (catch [IndexError] (assert false)) + (except [NameError] (pass))) + + (try + (get [1] 3) + (except [e IndexError] (assert (isinstance e IndexError)))) + + (try + (get [1] 3) + (catch [e [IndexError NameError]] (assert (isinstance e IndexError)))) + + (try + (print foobar42ofthebaz) + (except [e [IndexError NameError]] (assert (isinstance e NameError)))) + + (try + (print foobar42) + (catch [[IndexError NameError]] (pass))) + + (try + (get [1] 3) + (catch [[IndexError NameError]] (pass))) + + (try + (print foobar42ofthebaz) + (catch)) + + (try + (print foobar42ofthebaz) + (except [])) + + (try + (print foobar42ofthebaz) + (except [] (pass))) + + (try + (print foobar42ofthebaz) + (catch [] + (setv foobar42ofthebaz 42) + (assert (= foobar42ofthebaz 42)))) + + (let [[passed false]] + (try + (try (pass) (except) (else (bla))) + (except [NameError] (setv passed true))) + (assert passed)) + + (let [[x 0]] + (try + (raise IOError) + (except [IOError] + (setv x 45)) + (else (setv x 44))) + (assert (= x 45))) + + (let [[x 0]] + (try + (raise KeyError) + (except [] + (setv x 45)) + (else (setv x 44))) + (assert (= x 45))) + + (let [[x 0]] + (try + (try + (raise KeyError) + (except [IOError] + (setv x 45)) + (else (setv x 44))) + (except)) + (assert (= x 0)))) (defn test-earmuffs [] "NATIVE: Test earmuffs" @@ -146,6 +303,12 @@ ["X" "B" "C" "D"]))) +(defn test-tail-threading [] + "NATIVE: test tail threading macro" + (assert (= (.join ", " (* 10 ["foo"])) + (->> ["foo"] (* 10) (.join ", "))))) + + (defn test-threading-two [] "NATIVE: test threading macro" (assert (= (-> "a b c d" .upper (.replace "A" "X") .split) @@ -198,8 +361,18 @@ (defn test-context [] "NATIVE: test with" - (with-as (open "README.md" "r") fd - (pass))) + (with [fd (open "README.md" "r")] (assert fd)) + (with [(open "README.md" "r")] (pass))) + + +(defn test-for-doodle [] + "NATIVE: test for-do" + (do (do (do (do (do (do (do (do (do (setf (, x y) (, 0 0))))))))))) + (foreach [- [1 2]] + (do + (setf x (+ x 1)) + (setf y (+ y 1)))) + (assert (= y x 2))) (defn test-comprehensions [] @@ -207,4 +380,146 @@ (assert (= (list-comp (* x 2) (x (range 2))) [0 2])) (assert (= (list-comp (* x 2) (x (range 4)) (% x 2)) [2 6])) (assert (= (sorted (list-comp (* y 2) ((, x y) (.items {"1" 1 "2" 2})))) - [2 4]))) + [2 4])) + (assert (= (list-comp (, x y) (x (range 2) y (range 2))) + [(, 0 0) (, 0 1) (, 1 0) (, 1 1)]))) + + +(defn test-defn-order [] + "NATIVE: test defn evaluation order" + (setv acc []) + (defn my-fun [] + (.append acc "Foo") + (.append acc "Bar") + (.append acc "Baz")) + (my-fun) + (assert (= acc ["Foo" "Bar" "Baz"]))) + + +(defn test-defn-return [] + "NATIVE: test defn return" + (defn my-fun [x] + (+ x 1)) + (assert (= 43 (my-fun 42)))) + + +(defn test-defn-do [] + "NATIVE: test defn evaluation order with do" + (setv acc []) + (defn my-fun [] + (do + (.append acc "Foo") + (.append acc "Bar") + (.append acc "Baz"))) + (my-fun) + (assert (= acc ["Foo" "Bar" "Baz"]))) + + +(defn test-defn-do-return [] + "NATIVE: test defn return with do" + (defn my-fun [x] + (do + (+ x 42) ; noop + (+ x 1))) + (assert (= 43 (my-fun 42)))) + + +(defn test-mangles [] + "NATIVE: test mangles" + (assert (= 2 ((fn [] (+ 1 1)))))) + + +(defn test-fn-return [] + "NATIVE: test function return" + (setv fn-test ((fn [] (fn [] (+ 1 1))))) + (assert (= (fn-test) 2))) + + +(defn test-let [] + "NATIVE: test let works rightish" + (assert (= (let [[x 1] [y 2] [z 3]] (+ x y z)) 6))) + + +(defn test-if-mangler [] + "NATIVE: test that we return ifs" + (assert (= true (if true true true)))) + + +(defn test-nested-mangles [] + "NATIVE: test that we can use macros in mangled code" + (assert (= ((fn [] (-> 2 (+ 1 1) (* 1 2)))) 8))) + + +(defn test-let-scope [] + "NATIVE: test let works rightish" + (setv y 123) + (assert (= (let [[x 1] + [y 2] + [z 3]] + (+ x y z)) + 6)) + (try + (assert (= x 42)) ; This ain't true + (catch [e [NameError]] (assert e))) + (assert (= y 123))) + + +(defn test-symbol-utf-8 [] + "NATIVE: test symbol encoded" + (let [[♥ "love"] + [⚘ "flower"]] + (assert (= (+ ⚘ ♥) "flowerlove")))) + + +(defn test-symbol-dash [] + "NATIVE: test symbol encoded" + (let [[♥-♥ "doublelove"] + [-_- "what?"]] + (assert (= ♥-♥ "doublelove")) + (assert (= -_- "what?")))) + + +(defn test-and [] + "NATIVE: test the and function" + (let [[and123 (and 1 2 3)] + [and-false (and 1 False 3)]] + (assert (= and123 3)) + (assert (= and-false False)))) + + +(defn test-or [] + "NATIVE: test the or function" + (let [[or-all-true (or 1 2 3 True "string")] + [or-some-true (or False "hello")] + [or-none-true (or False False)]] + (assert (= or-all-true 1)) + (assert (= or-some-true "hello")) + (assert (= or-none-true False)))) + + +(defn test-if-return-branching [] + "NATIVE: test the if return branching" + ; thanks, algernon + (assert (= 1 (let [[x 1] + [y 2]] + (if true + 2) + 1))) + (assert (= 1 (let [[x 1] [y 2]] + (pass) + (pass) + ((fn [] 1)))))) + + +; FEATURE: native hy-eval +; +; - related to bug #64 +; - https://github.com/paultag/hy/issues/64 +; - https://github.com/paultag/hy/pull/62 +; +; (defn test-eval [] +; "NATIVE: test eval" +; (assert (= 1 (eval 1))) +; (assert (= "foobar" (eval "foobar"))) +; (setv x 42) +; (assert (= x (eval x)))) diff --git a/tests/resources/__init__.py b/tests/resources/__init__.py index b115480..bb532ec 100644 --- a/tests/resources/__init__.py +++ b/tests/resources/__init__.py @@ -2,3 +2,7 @@ def kwtest(*args, **kwargs): return kwargs + + +def function_with_a_dash(): + pass diff --git a/tox.ini b/tox.ini index bfb0641..1cf91e8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,11 @@ [tox] -envlist = py27,pypy,py32,py33,py26 +envlist = py27,pypy,py32,py33,py26,flake8 [testenv] commands = nosetests deps = nose setuptools + +[testenv:flake8] +deps = flake8 +commands = flake8 hy bin tests