Merge branch 'master' into pr/565

This commit is contained in:
Paul Tagliamonte 2014-05-12 20:43:09 -04:00
commit 1ae666e096
31 changed files with 507 additions and 293 deletions

View File

@ -1,19 +1,18 @@
language: python language: python
python: python:
- "pypy" - "pypy"
- "2.6"
- "2.7" - "2.7"
- "3.2" - "3.2"
- "3.3" - "3.3"
- "2.6" - "3.4"
# command to install dependencies # command to install dependencies
install: install:
- pip install -r requirements.txt - pip install -r requirements-dev.txt
- pip install coveralls - pip install coveralls
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse importlib unittest2 astor; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi # needs for running tests
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install astor; fi - pip install --allow-all-external -e .
- if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then pip install astor; fi # command to run tests
- python setup.py -q install
# # command to run tests
script: make travis script: make travis
after_success: coveralls after_success: coveralls
notifications: notifications:

View File

@ -45,3 +45,5 @@
* kirbyfan64 <kirbyfan64@users.noreply.github.com> * kirbyfan64 <kirbyfan64@users.noreply.github.com>
* Brendan Curran-Johnson <brendan@bcjbcj.ca> * Brendan Curran-Johnson <brendan@bcjbcj.ca>
* Ivan Kozik <ivan@ludios.org> * Ivan Kozik <ivan@ludios.org>
* Allison Kaptur <allison.kaptur@gmail.com>
* Matthew Wampler-Doty <matthew.wampler.doty@gmail.com>

44
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,44 @@
Contributions are welcome & greatly appreciated, every little bit
helps in making Hy more awesome.
Pull requests are great! We love them, here is a quick guide:
- Fork the repo, create a topic branch for a feature/fix. Avoid
making changes directly on the master branch
- All incoming features should be accompanied with tests
- Before you submit a PR, please run the tests and check your code
against the style guide. You can do both these things at once::
$ make d
- Make commits into logical units, so that it is easier to track &
navigate later. Before submitting a PR, try squashing the commits
into changesets that are easy to come back to later. Also make sure
you don't leave spurious whitespace in the changesets, this avoids
creation of whitespace fix commits later.
- As far as commit messages go, try to adhere to
the following:
+ Try sticking to the 50 character limit for the first line of git
commit messages
+ For more explanations etc. follow this up with a blank line and
continue describing the commit in detail
- Finally add yourself to the AUTHORS file (as a separate commit), you
deserve it :)
- All incoming changes need to be acked by 2 different members of
Hylang's core team. Additional review is clearly welcome, but we need
a minimum of 2 signoffs for any change.
- If a core member is sending in a PR, please find 2 core members that doesn't
include the PR submitter. The idea here is that one can work with the PR
author, and a second acks the entire change set.
- For documentation & other trivial changes, we're good to merge after one
ACK. We've got low coverage, so it'd be great to keep that barrier low.

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
# Base image
#
# VERSION 0.1
FROM debian:unstable
MAINTAINER Paul R. Tagliamonte <paultag@debian.org>
RUN apt-get update && apt-get install -y python3.4 python3-pip
ADD . /opt/hylang/hy
RUN python3.4 /usr/bin/pip3 install -e /opt/hylang/hy
CMD ["hy"]

12
NEWS
View File

@ -60,7 +60,7 @@ Changes from 0.9.12
* "clean" target added to Makefile * "clean" target added to Makefile
* hy2py supports a bunch of commandline options to show AST, source etc. * hy2py supports a bunch of commandline options to show AST, source etc.
* Sub-object mangling: every identifier is split along the dots & mangled * Sub-object mangling: every identifier is split along the dots & mangled
seperately separately
[ Bug Fixes ] [ Bug Fixes ]
* Empty MacroExpansions work as expected * Empty MacroExpansions work as expected
@ -88,7 +88,7 @@ Changes from Hy 0.9.11
what exactly was added. what exactly was added.
The biggest feature, Reader Macros, landed later The biggest feature, Reader Macros, landed later
in the cycle, but were big enough to warrent a release on it's in the cycle, but were big enough to warrant a release on its
own. A huge thanks goes to Foxboron for implementing them own. A huge thanks goes to Foxboron for implementing them
and a massive hug goes out to olasd for providing ongoing and a massive hug goes out to olasd for providing ongoing
reviews during the development. reviews during the development.
@ -267,7 +267,7 @@ Changes from Hy 0.9.6
* UTF-8 encoded hy symbols are now hy_... rather than __hy_..., it's * UTF-8 encoded hy symbols are now hy_... rather than __hy_..., it's
silly to prefex them as such. (PT) silly to prefex them as such. (PT)
* `j' is no longer always intepreted as a complex number; we use it much * `j' is no longer always interpreted as a complex number; we use it much
more as a symbol. (ND) more as a symbol. (ND)
* (decorate-with) has been moved to (with-decorate) (JD) * (decorate-with) has been moved to (with-decorate) (JD)
* New (unless) macro (JD) * New (unless) macro (JD)
@ -286,7 +286,7 @@ Changes from Hy 0.9.6
and jd for making this happen. This solves just an insane number and jd for making this happen. This solves just an insane number
of bugs. (ND, PT, JD) of bugs. (ND, PT, JD)
* Eval no longer sucks with statements (ND) * Eval no longer sucks with statements (ND)
* New magic binary flags / mis fixes with the hy intepreter * New magic binary flags / mis fixes with the hy interpreter
(WKG + @eigenhombre) (WKG + @eigenhombre)
@ -311,7 +311,7 @@ Changes from Hy 0.9.5
=============== WARNING: WARNING: READ ME: READ ME: =================== =============== WARNING: WARNING: READ ME: READ ME: ===================
From here on out, we will only support "future division" as part of hy. From here on out, we will only support "future division" as part of hy.
This is actually quite a pain for us, but it's going to be quite an This is actually quite a pain for us, but it's going to be quite an
amazing feautre. amazing feature.
This also normalizes behavior from Py 2 --> Py 3. This also normalizes behavior from Py 2 --> Py 3.
@ -346,7 +346,7 @@ Changes from Hy 0.9.4
* Statements in the `fn' path early will not return anymore. (PT) * Statements in the `fn' path early will not return anymore. (PT)
* Added "not" as the inline "not" operator. It's advised to still * Added "not" as the inline "not" operator. It's advised to still
use "not-in" or "is-not" rather then nesting. (JD) use "not-in" or "is-not" rather than nesting. (JD)
* `let' macro added (PT) * `let' macro added (PT)
* Added "~" as the "invert" operator. (JD) * Added "~" as the "invert" operator. (JD)
* `catch' now accepts a new format: (JD) * `catch' now accepts a new format: (JD)

View File

@ -25,7 +25,7 @@ OK, so, why?
------------ ------------
Well. Python is awesome. So awesome, that we have so many tools to alter the Well. Python is awesome. So awesome, that we have so many tools to alter the
languge in a *core* way, but we never use them. language in a *core* way, but we never use them.
Why? Why?

View File

@ -1,40 +0,0 @@
#!/usr/bin/env python
from __future__ import print_function
from hy.importer import import_file_to_ast, import_file_to_hst
import argparse
import sys
import astor.codegen
module_name = "<STDIN>"
parser = argparse.ArgumentParser(
prog="hy2py",
usage="%(prog)s [options] FILE",
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--with-source", "-s", action="store_true",
help="Show the parsed source structure")
parser.add_argument("--with-ast", "-a", action="store_true",
help="Show the generated AST")
parser.add_argument("--without-python", "-np", action="store_true",
help="Do not show the python code generated from the AST")
parser.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS)
options = parser.parse_args(sys.argv[1:])
if options.with_source:
hst = import_file_to_hst(options.args[0])
print(hst)
print()
print()
_ast = import_file_to_ast(options.args[0], module_name)
if options.with_ast:
print(astor.dump(_ast))
print()
print()
if not options.without_python:
print(astor.codegen.to_source(_ast))

View File

@ -96,7 +96,13 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
try:
import sphinx_rtd_theme
except ImportError:
html_theme = 'default' html_theme = 'default'
else:
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the

View File

@ -89,26 +89,10 @@ To build the docs in HTML::
Write docs---docs are good! Even this doc! Write docs---docs are good! Even this doc!
Core Development Rules Contributing
====================== ============
Pull requests are good!
Before you submit a PR, please run the tests and check your code against the style guide. You can do both these things at once::
$ make d
All incoming changes need to be acked by 2 different members of Hylang's
core team. Additional review is clearly welcome, but we need a minimum of
2 signoffs for any change.
If a core member is sending in a PR, please find 2 core members that don't
include the PR submitter. The idea here is that one can work with the PR
author, and a second acks the entire change set.
If the change is adding documentation, feel free to just merge after one
ACK. We've got low coverage, so it'd be great to keep that barrier low.
.. include:: ../CONTRIBUTING.rst
Core Team Core Team
========= =========

View File

@ -271,6 +271,21 @@ however is called only for every other value in the list.
(side-effect2 x))) (side-effect2 x)))
dict-comp
---------
`dict-comp` is used to create dictionaries. It takes three or four parameters.
The first two parameters are for controlling the return value
(key-value pair), while the third is used to select items from a sequence. The
fourth and optional parameter can be used to filter out some of the items in
the sequence based on a conditional expression.
.. code-block:: hy
=> (dict-comp x (* x 2) [x (range 10)] (odd? x))
{1: 2, 3: 6, 9: 18, 5: 10, 7: 14}
do / progn do / progn
---------- ----------
@ -441,6 +456,44 @@ symbols for function names as the first parameter, `defn-alias` and
=> (alias) => (alias)
"Hello!" "Hello!"
defmain
-------
.. versionadded:: 0.10.1
The `defmain` macro defines a main function that is immediately called
with sys.argv as arguments if and only if this file is being executed
as a script. In other words this:
.. code-block:: clj
(defmain [&rest args]
(do-something-with args))
is the equivalent of::
def main(*args):
do_something_with(args)
return 0
if __name__ == "__main__":
import sys
retval = main(*sys.arg)
if isinstance(retval, int):
sys.exit(retval)
Note, as you can see above, if you return an integer from this
function, this will be used as the exit status for your script.
(Python defaults to exit status 0 otherwise, which means everything's
okay!)
(Since (sys.exit 0) is not run explicitly in case of a non-integer
return from defmain, it's good to put (defmain) as the last bit of
code in your file.)
.. _defmacro: .. _defmacro:
defmacro defmacro
@ -629,6 +682,24 @@ normally. If the execution is halted with `break`, the `else` does not execute.
loop finished loop finished
genexpr
-------
`genexpr` is used to create generator expressions. It takes two or three parameters.
The first parameter is the expression controlling the return value, while
the second is used to select items from a list. The third and optional
parameter can be used to filter out some of the items in the list based on a
conditional expression. `genexpr` is similar to `list-comp`, except that it returns
an iterable that evaluates values one by one instead of evaluating them immediately.
.. code-block:: hy
=> (def collection (range 10))
=> (def filtered (genexpr x [x collection] (even? x)))
=> (list filtered)
[0, 2, 4, 6, 8]
.. _gensym: .. _gensym:
gensym gensym
@ -1003,6 +1074,22 @@ element:
[1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9]
set-comp
--------
`set-comp` is used to create sets. It takes two or three parameters.
The first parameter is for controlling the return value, while the second is
used to select items from a sequence. The third and optional parameter can be
used to filter out some of the items in the sequence based on a conditional
expression.
.. code-block:: hy
=> (setv data [1 2 3 4 5 2 3 4 5 3 4 5])
=> (set-comp x [x data] (odd? x))
{1, 3, 5}
slice slice
----- -----
@ -1057,7 +1144,7 @@ Example usage
; Throw an IOError("foobar") ; Throw an IOError("foobar")
`throw` can acccept a single argument (an `Exception` class or instance), or `throw` can accept a single argument (an `Exception` class or instance), or
no arguments to re-raise the last Exception. no arguments to re-raise the last Exception.
@ -1151,7 +1238,7 @@ while
`while` form is used to execute a single or more blocks as long as a condition `while` form is used to execute a single or more blocks as long as a condition
is being met. is being met.
The following example will output "hello world!" on screen indefinetely: The following example will output "hello world!" on screen indefinitely:
.. code-block:: clj .. code-block:: clj
@ -1273,25 +1360,3 @@ yield-from
want your coroutine to be able to delegate its processes to another want your coroutine to be able to delegate its processes to another
coroutine, say if using something fancy like coroutine, say if using something fancy like
`asyncio <http://docs.python.org/3.4/library/asyncio.html>`_. `asyncio <http://docs.python.org/3.4/library/asyncio.html>`_.
.. _zipwith:
zipwith
-------
.. versionadded:: 0.10.0
`zipwith` zips multiple lists and maps the given function over the result. It is
equilavent to calling ``zip``, followed by calling ``map`` on the result.
In the following example, `zipwith` is used to add the contents of two lists
together. The equilavent ``map`` and ``zip`` calls follow.
.. code-block:: clj
=> (import operator.add)
=> (zipwith operator.add [1 2 3] [4 5 6]) ; using zipwith
[5, 7, 9]
=> (map operator.add (zip [1 2 3] [4 5 6])) ; using map+zip
[5, 7, 9]

View File

@ -2,6 +2,8 @@
Command Line Interface Command Line Interface
====================== ======================
.. _hy:
hy hy
-- --
@ -46,6 +48,8 @@ Command line options
Print the Hy version number and exit. Print the Hy version number and exit.
.. _hyc:
hyc hyc
--- ---
@ -71,3 +75,29 @@ Command line options
$ hyc hyname.hy $ hyc hyname.hy
$ python hyname.pyc $ python hyname.pyc
Hy Afroman! Hy Afroman!
.. _hy2py:
hy2py
-----
.. versionadded:: 0.10.1
Command line options
^^^^^^^^^^^^^^^^^^^^
.. cmdoption:: -s
--with-source
Show the parsed source structure.
.. cmdoption:: -a
--with-ast
Show the generated AST.
.. cmdoption:: -np
--without-python
Do not show the Python code generated from the AST.

View File

@ -495,8 +495,8 @@ nth
Usage: ``(nth coll n)`` Usage: ``(nth coll n)``
Return the `nth` item in a collection, counting from 0. Unlike Return the `nth` item in a collection, counting from 0. Unlike
``get``, ``nth`` works on both iterators and iterables. Returns ``None`` ``get``, ``nth`` works on both iterators and iterables. Raises ``IndexError``
if the `n` is outside the range of `coll`. if the `n` is outside the range of ``coll`` or ``ValueError`` if it's negative.
.. code-block:: hy .. code-block:: hy
@ -506,8 +506,10 @@ if the `n` is outside the range of `coll`.
=> (nth [1 2 4 7] 3) => (nth [1 2 4 7] 3)
7 7
=> (none? (nth [1 2 4 7] 5)) => (nth [1 2 4 7] 5)
True Traceback (most recent call last):
...
IndexError: 5
=> (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2)) => (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2))
5 5
@ -764,6 +766,7 @@ drop
Usage: ``(drop n coll)`` Usage: ``(drop n coll)``
Return an iterator, skipping the first ``n`` members of ``coll`` Return an iterator, skipping the first ``n`` members of ``coll``
Raises ``ValueError`` if ``n`` is negative.
.. code-block:: hy .. code-block:: hy
@ -924,6 +927,7 @@ take
Usage: ``(take n coll)`` Usage: ``(take n coll)``
Return an iterator containing the first ``n`` members of ``coll``. Return an iterator containing the first ``n`` members of ``coll``.
Raises ``ValueError`` if ``n`` is negative.
.. code-block:: hy .. code-block:: hy
@ -979,3 +983,21 @@ Return an iterator from ``coll`` as long as predicate, ``pred`` returns True.
=> (list (take-while neg? [ 1 2 3 -4 5])) => (list (take-while neg? [ 1 2 3 -4 5]))
[] []
.. _zipwith:
zipwith
-------
.. versionadded:: 0.9.13
Usage: ``(zipwith fn coll ...)``
Equivalent to ``zip``, but uses a multi-argument function instead of creating a tuple.
If ``zipwith`` is called with N collections, then ``fn`` must accept N arguments.
.. code-block:: clojure
=> (import operator)
=> (list (zipwith operator.add [1 2 3] [4 5 6]))
[5, 7, 9]

View File

@ -387,7 +387,7 @@ for a more complete description.) ``nif`` is an example, something like a numeri
where based on the expression, one of the 3 forms is called depending on if the where based on the expression, one of the 3 forms is called depending on if the
expression is positive, zero or negative. expression is positive, zero or negative.
A first pass might be someting like: A first pass might be something like:
.. code-block:: hy .. code-block:: hy

View File

@ -70,4 +70,4 @@ and expression is passed to the correct function.
.. warning:: .. warning::
Because of a limitation in Hy's lexer and parser, reader macros can't Because of a limitation in Hy's lexer and parser, reader macros can't
redefine defined syntax such as ``()[]{}``. This will most likely be redefine defined syntax such as ``()[]{}``. This will most likely be
adressed in the future. addressed in the future.

View File

@ -500,7 +500,7 @@ Let's take the classic:
(loop (print (eval (read)))) (loop (print (eval (read))))
Rather then write it like that, we can write it as follows: Rather than write it like that, we can write it as follows:
.. code-block:: clj .. code-block:: clj

View File

@ -1,6 +1,6 @@
#!/usr/bin/env hy #!/usr/bin/env hy
;; Copyright (c) Paul R. Tagliamonte <paultag@debian.org>, 2013 under the terms ;; Copyright (c) Paul R. Tagliamonte <paultag@debian.org>, 2013 under the terms
;; of the Expat license, a copy of which you have should have recieved with ;; of the Expat license, a copy of which you should have received with
;; the source. ;; the source.
(import sys) (import sys)

View File

@ -23,7 +23,7 @@ import imp
import sys import sys
# This just mocks the normalish behavior of the Python interp. Helpful to aid # This just mocks the normalish behavior of the Python interp. Helpful to aid
# with shiming existing apps that don't really "work" with Hy. # with shimming existing apps that don't really "work" with Hy.
# #
# You could say this script helps Hyjack a file. # You could say this script helps Hyjack a file.
# #

View File

@ -25,16 +25,21 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE. # DEALINGS IN THE SOFTWARE.
from __future__ import print_function
import argparse import argparse
import code import code
import ast import ast
import sys import sys
import astor.codegen
import hy import hy
from hy.lex import LexException, PrematureEndOfInput, tokenize from hy.lex import LexException, PrematureEndOfInput, tokenize
from hy.compiler import hy_compile, HyTypeError from hy.compiler import hy_compile, HyTypeError
from hy.importer import ast_compile, import_buffer_to_module from hy.importer import (ast_compile, import_buffer_to_module,
import_file_to_ast, import_file_to_hst)
from hy.completer import completion from hy.completer import completion
from hy.macros import macro, require from hy.macros import macro, require
@ -66,7 +71,6 @@ builtins.exit = HyQuitter('exit')
def print_python_code(_ast): def print_python_code(_ast):
import astor.codegen
# astor cannot handle ast.Interactive, so disguise it as a module # astor cannot handle ast.Interactive, so disguise it as a module
_ast_for_print = ast.Module() _ast_for_print = ast.Module()
_ast_for_print.body = _ast.body _ast_for_print.body = _ast.body
@ -313,3 +317,43 @@ def hyc_main():
sys.stderr.write("hyc: Can't open file '%s': [Errno %d] %s\n" % sys.stderr.write("hyc: Can't open file '%s': [Errno %d] %s\n" %
(x.filename, x.errno, x.strerror)) (x.filename, x.errno, x.strerror))
sys.exit(x.errno) sys.exit(x.errno)
# entry point for cmd line script "hy2py"
def hy2py_main():
module_name = "<STDIN>"
options = dict(prog="hy2py", usage="%(prog)s [options] FILE",
formatter_class=argparse.RawDescriptionHelpFormatter)
parser = argparse.ArgumentParser(**options)
parser.add_argument("--with-source", "-s", action="store_true",
help="Show the parsed source structure")
parser.add_argument("--with-ast", "-a", action="store_true",
help="Show the generated AST")
parser.add_argument("--without-python", "-np", action="store_true",
help=("Do not show the Python code generated "
"from the AST"))
parser.add_argument('args', nargs=argparse.REMAINDER,
help=argparse.SUPPRESS)
options = parser.parse_args(sys.argv[1:])
if not options.args:
parser.exit(1, parser.format_help())
if options.with_source:
hst = import_file_to_hst(options.args[0])
print(hst)
print()
print()
_ast = import_file_to_ast(options.args[0], module_name)
if options.with_ast:
print(astor.dump(_ast))
print()
print()
if not options.without_python:
print(astor.codegen.to_source(_ast))
parser.exit(0)

View File

@ -732,25 +732,10 @@ class HyASTCompiler(object):
handler_results += self._compile_catch_expression(e, name) handler_results += self._compile_catch_expression(e, name)
handlers.append(handler_results.stmts.pop()) handlers.append(handler_results.stmts.pop())
elif e[0] == HySymbol("else"): elif e[0] == HySymbol("else"):
if orelse: orelse = self.try_except_helper(e, HySymbol("else"), orelse)
raise HyTypeError(
e,
"`try' cannot have more than one `else'")
else:
orelse = self._compile_branch(e[1:])
# XXX tempvar magic
orelse += orelse.expr_as_stmt()
orelse = orelse.stmts
elif e[0] == HySymbol("finally"): elif e[0] == HySymbol("finally"):
if finalbody: finalbody = self.try_except_helper(e, HySymbol("finally"),
raise HyTypeError( finalbody)
e,
"`try' cannot have more than one `finally'")
else:
finalbody = self._compile_branch(e[1:])
# XXX tempvar magic
finalbody += finalbody.expr_as_stmt()
finalbody = finalbody.stmts
else: else:
raise HyTypeError(e, "Unknown expression in `try'") raise HyTypeError(e, "Unknown expression in `try'")
@ -768,7 +753,7 @@ class HyASTCompiler(object):
col_offset=expr.start_column, col_offset=expr.start_column,
type=None, type=None,
name=None, name=None,
body=[ast.Pass(lineno=expr.start_line, body=[ast.Raise(lineno=expr.start_line,
col_offset=expr.start_column)])] col_offset=expr.start_column)])]
ret = handler_results ret = handler_results
@ -809,6 +794,17 @@ class HyASTCompiler(object):
body=body, body=body,
orelse=orelse) + returnable orelse=orelse) + returnable
def try_except_helper(self, hy_obj, symbol, accumulated):
if accumulated:
raise HyTypeError(
hy_obj,
"`try' cannot have more than one `%s'" % symbol)
else:
accumulated = self._compile_branch(hy_obj[1:])
accumulated += accumulated.expr_as_stmt()
accumulated = accumulated.stmts
return accumulated
@builds("except") @builds("except")
@builds("catch") @builds("catch")
def magic_internal_form(self, expr): def magic_internal_form(self, expr):
@ -1516,7 +1512,7 @@ class HyASTCompiler(object):
def compile_require(self, expression): def compile_require(self, expression):
""" """
TODO: keep track of what we've imported in this run and then TODO: keep track of what we've imported in this run and then
"unimport" it after we've completed `thing' so that we don't polute "unimport" it after we've completed `thing' so that we don't pollute
other envs. other envs.
""" """
expression.pop(0) expression.pop(0)
@ -1755,18 +1751,19 @@ class HyASTCompiler(object):
def _compile_assign(self, name, result, def _compile_assign(self, name, result,
start_line, start_column): start_line, start_column):
result = self.compile(result) result = self.compile(result)
if result.temp_variables and isinstance(name, HyString):
result.rename(name)
return result
ld_name = self.compile(name) ld_name = self.compile(name)
st_name = self._storeize(ld_name)
if result.temp_variables \
and isinstance(name, HyString) \
and '.' not in name:
result.rename(name)
else:
st_name = self._storeize(ld_name)
result += ast.Assign( result += ast.Assign(
lineno=start_line, lineno=start_line,
col_offset=start_column, col_offset=start_column,
targets=[st_name], value=result.force_expr) targets=[st_name],
value=result.force_expr)
result += ld_name result += ld_name
return result return result
@ -1859,7 +1856,7 @@ class HyASTCompiler(object):
ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist) ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist)
if PY34: if PY34:
# Python 3.4+ requres that args are an ast.arg object, rather # Python 3.4+ requires that args are an ast.arg object, rather
# than an ast.Name or bare string. # than an ast.Name or bare string.
args = [ast.arg(arg=ast_str(x), args = [ast.arg(arg=ast_str(x),
annotation=None, # Fix me! annotation=None, # Fix me!

View File

@ -23,7 +23,9 @@
;;;; to make functional programming slightly easier. ;;;; to make functional programming slightly easier.
;;;; ;;;;
(import itertools)
(import functools)
(import collections)
(import [hy._compat [long-type]]) ; long for python2, int for python3 (import [hy._compat [long-type]]) ; long for python2, int for python3
(import [hy.models.cons [HyCons]]) (import [hy.models.cons [HyCons]])
@ -44,15 +46,11 @@
"Check whether c can be used as a cons object" "Check whether c can be used as a cons object"
(instance? HyCons c)) (instance? HyCons c))
(defn cycle [coll] (defn keyword? [k]
"Yield an infinite repetition of the items in coll" "Check whether k is a keyword"
(setv seen []) (and (instance? (type :foo) k)
(for* [x coll] (.startswith k (get :foo 0))))
(yield x)
(.append seen x))
(while seen
(for* [x seen]
(yield x))))
(defn dec [n] (defn dec [n]
"Decrement n by 1" "Decrement n by 1"
@ -82,22 +80,36 @@
(yield val) (yield val)
(.add seen val)))))) (.add seen val))))))
(if-python2
(do
(setv filterfalse itertools.ifilterfalse)
(setv zip_longest itertools.izip_longest)
(setv filter itertools.ifilter)
(setv map itertools.imap)
(setv zip itertools.izip)
(setv range xrange)
(setv input raw_input))
(do
(setv reduce functools.reduce)
(setv filterfalse itertools.filterfalse)
(setv zip_longest itertools.zip_longest)
; Someone can import these directly from `hy.core.language`;
; we'll make some duplicates.
(setv filter filter)
(setv map map)
(setv zip zip)
(setv range range)
(setv input input)))
(setv cycle itertools.cycle)
(setv repeat itertools.repeat)
(setv drop-while itertools.dropwhile)
(setv take-while itertools.takewhile)
(setv zipwith map)
(defn drop [count coll] (defn drop [count coll]
"Drop `count` elements from `coll` and yield back the rest" "Drop `count` elements from `coll` and yield back the rest"
(let [[citer (iter coll)]] (itertools.islice coll count nil))
(try (for* [i (range count)]
(next citer))
(catch [StopIteration]))
citer))
(defn drop-while [pred coll]
"Drop all elements of `coll` until `pred` is False"
(let [[citer (iter coll)]]
(for* [val citer]
(if (not (pred val))
(do (yield val) (break))))
(for* [val citer]
(yield val))))
(defn empty? [coll] (defn empty? [coll]
"Return True if `coll` is empty" "Return True if `coll` is empty"
@ -121,13 +133,6 @@
(if (not (hasattr tree attr)) (if (not (hasattr tree attr))
(setattr tree attr 1)))) (setattr tree attr 1))))
(defn filter [pred coll]
"Return all elements from `coll` that pass `pred`"
(let [[citer (iter coll)]]
(for* [val citer]
(if (pred val)
(yield val)))))
(defn flatten [coll] (defn flatten [coll]
"Return a single flat list expanding all members of coll" "Return a single flat list expanding all members of coll"
(if (coll? coll) (if (coll? coll)
@ -169,7 +174,7 @@
(defn first [coll] (defn first [coll]
"Return first item from `coll`" "Return first item from `coll`"
(get coll 0)) (nth coll 0))
(defn identity [x] (defn identity [x]
"Returns the argument unchanged" "Returns the argument unchanged"
@ -200,8 +205,7 @@
(defn iterable? [x] (defn iterable? [x]
"Return true if x is iterable" "Return true if x is iterable"
(try (do (iter x) true) (isinstance x collections.Iterable))
(catch [Exception] false)))
(defn iterate [f x] (defn iterate [f x]
(setv val x) (setv val x)
@ -211,8 +215,7 @@
(defn iterator? [x] (defn iterator? [x]
"Return true if x is an iterator" "Return true if x is an iterator"
(try (= x (iter x)) (isinstance x collections.Iterator))
(catch [TypeError] false)))
(defn list* [hd &rest tl] (defn list* [hd &rest tl]
"Return a dotted list construed from the elements of the argument" "Return a dotted list construed from the elements of the argument"
@ -253,13 +256,9 @@
(defn nth [coll index] (defn nth [coll index]
"Return nth item in collection or sequence, counting from 0" "Return nth item in collection or sequence, counting from 0"
(if (not (neg? index)) (try
(if (iterable? coll) (next (drop index coll))
(try (get (list (take 1 (drop index coll))) 0) (catch [e StopIteration] (raise (IndexError index)))))
(catch [IndexError] None))
(try (get coll index)
(catch [IndexError] None)))
None))
(defn odd? [n] (defn odd? [n]
"Return true if n is an odd number" "Return true if n is an odd number"
@ -280,14 +279,7 @@
(defn rest [coll] (defn rest [coll]
"Get all the elements of a coll, except the first." "Get all the elements of a coll, except the first."
(slice coll 1)) (drop 1 coll))
(defn repeat [x &optional n]
"Yield x forever or optionally n times"
(if (none? n)
(setv dispatch (fn [] (while true (yield x))))
(setv dispatch (fn [] (for* [_ (range n)] (yield x)))))
(dispatch))
(defn repeatedly [func] (defn repeatedly [func]
"Yield result of running func repeatedly" "Yield result of running func repeatedly"
@ -296,7 +288,7 @@
(defn second [coll] (defn second [coll]
"Return second item from `coll`" "Return second item from `coll`"
(get coll 1)) (nth coll 1))
(defn some [pred coll] (defn some [pred coll]
"Return true if (pred x) is logical true for any x in coll, else false" "Return true if (pred x) is logical true for any x in coll, else false"
@ -317,9 +309,7 @@
(defn take [count coll] (defn take [count coll]
"Take `count` elements from `coll`, or the whole set if the total "Take `count` elements from `coll`, or the whole set if the total
number of entries in `coll` is less than `count`." number of entries in `coll` is less than `count`."
(let [[citer (iter coll)]] (itertools.islice coll nil count))
(for* [_ (range count)]
(yield (next citer)))))
(defn take-nth [n coll] (defn take-nth [n coll]
"Return every nth member of coll "Return every nth member of coll
@ -332,29 +322,15 @@
(next citer)))) (next citer))))
(raise (ValueError "n must be positive")))) (raise (ValueError "n must be positive"))))
(defn take-while [pred coll]
"Take all elements while `pred` is true"
(let [[citer (iter coll)]]
(for* [val citer]
(if (pred val)
(yield val)
(break)))))
(defn zero? [n] (defn zero? [n]
"Return true if n is 0" "Return true if n is 0"
(_numeric_check n) (_numeric_check n)
(= n 0)) (= n 0))
(defn zipwith [func &rest lists]
"Zip the contents of several lists and map a function to the result"
(do
(import functools)
(map (functools.partial (fn [f args] (apply f args)) func) (apply zip lists))))
(def *exports* '[calling-module-name coll? cons cons? cycle dec distinct (def *exports* '[calling-module-name coll? cons cons? cycle dec distinct
disassemble drop drop-while empty? even? every? first filter disassemble drop drop-while empty? even? every? first filter
flatten float? gensym identity inc instance? integer flatten float? gensym identity inc instance? integer
integer? integer-char? iterable? iterate iterator? integer? integer-char? iterable? iterate iterator? keyword?
list* macroexpand macroexpand-1 neg? nil? none? nth list* macroexpand macroexpand-1 map neg? nil? none? nth
numeric? odd? pos? remove repeat repeatedly rest second numeric? odd? pos? range remove repeat repeatedly rest second
some string string? take take-nth take-while zero? zipwith]) some string string? take take-nth take-while zero? zip zipwith])

View File

@ -31,34 +31,6 @@
[hy._compat [PY33 PY34]]) [hy._compat [PY33 PY34]])
(defmacro for [args &rest body]
"shorthand for nested for loops:
(for [x foo
y bar]
baz) ->
(for* [x foo]
(for* [y bar]
baz))"
(if (odd? (len args))
(macro-error args "`for' requires an even number of args."))
(if (empty? body)
(macro-error None "`for' requires a body to evaluate"))
(if (empty? args)
`(do ~@body)
(if (= (len args) 2)
; basecase, let's just slip right in.
`(for* [~@args] ~@body)
; otherwise, let's do some legit handling.
(let [[alist (slice args 0 nil 2)]
[ilist (slice args 1 nil 2)]]
`(do
(import itertools)
(for* [(, ~@alist) (itertools.product ~@ilist)] ~@body))))))
(defmacro with [args &rest body] (defmacro with [args &rest body]
"shorthand for nested for* loops: "shorthand for nested for* loops:
(with [[x foo] [y bar]] baz) -> (with [[x foo] [y bar]] baz) ->
@ -116,6 +88,26 @@
root) root)
(defmacro for [args &rest body]
"shorthand for nested for loops:
(for [x foo
y bar]
baz) ->
(for* [x foo]
(for* [y bar]
baz))"
(cond
[(odd? (len args))
(macro-error args "`for' requires an even number of args.")]
[(empty? body)
(macro-error None "`for' requires a body to evaluate")]
[(empty? args) `(do ~@body)]
[(= (len args) 2) `(for* [~@args] ~@body)]
[true
(let [[alist (slice args 0 nil 2)]]
`(for* [(, ~@alist) (genexpr (, ~@alist) [~@args])] ~@body))]))
(defmacro -> [head &rest rest] (defmacro -> [head &rest rest]
;; TODO: fix the docstring by someone who understands this ;; TODO: fix the docstring by someone who understands this
(setv ret head) (setv ret head)
@ -170,6 +162,21 @@
(let ~(HyList (map (fn [x] `[~x (gensym (slice '~x 2))]) syms)) (let ~(HyList (map (fn [x] `[~x (gensym (slice '~x 2))]) syms))
~@body)))) ~@body))))
(defmacro defmain [args &rest body]
"Write a function named \"main\" and do the if __main__ dance"
(let [[retval (gensym)]]
`(do
(defn main [~@args]
~@body)
(when (= --name-- "__main__")
(import sys)
(setv ~retval (apply main sys.argv))
(if (integer? ~retval)
(sys.exit ~retval))))))
(defmacro-alias [defn-alias defun-alias] [names lambda-list &rest body] (defmacro-alias [defn-alias defun-alias] [names lambda-list &rest body]
"define one function with several names" "define one function with several names"
(let [[main (first names)] (let [[main (first names)]

View File

@ -28,7 +28,7 @@ import traceback
class HyError(Exception): class HyError(Exception):
""" """
Generic Hy error. All interal Exceptions will be subclassed from this Generic Hy error. All internal Exceptions will be subclassed from this
Exception. Exception.
""" """
pass pass

View File

@ -1,6 +1,12 @@
-r requirements.txt # test tools
tox
nose nose
tox
# code quality
flake8
coverage
# documentation
Pygments>=1.6 Pygments>=1.6
Sphinx Sphinx
coverage sphinx_rtd_theme

View File

@ -1,3 +0,0 @@
# Check site / dev for more deps!
flake8
rply>=0.7.0

View File

@ -59,7 +59,8 @@ setup(
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'hy = hy.cmdline:hy_main', 'hy = hy.cmdline:hy_main',
'hyc = hy.cmdline:hyc_main' 'hyc = hy.cmdline:hyc_main',
'hy2py = hy.cmdline:hy2py_main',
] ]
}, },
packages=find_packages(exclude=['tests*']), packages=find_packages(exclude=['tests*']),

View File

@ -57,6 +57,34 @@
(try (do (dec None) (assert False)) (try (do (dec None) (assert False))
(catch [e [TypeError]] (assert (in "not a number" (str e)))))) (catch [e [TypeError]] (assert (in "not a number" (str e))))))
(defn test-setv []
"NATIVE: testing setv mutation"
(setv x 1)
(setv y 1)
(assert-equal x y)
(setv x (setv y 12))
(assert-equal x 12)
(assert-equal y 12)
(setv x (setv y (fn [x] 9)))
(assert-equal (x y) 9)
(assert-equal (y x) 9)
(try (do (setv a.b 1) (assert False))
(catch [e [NameError]] (assert (in "name 'a' is not defined" (str e)))))
(try (do (setv b.a (fn [x] x)) (assert False))
(catch [e [NameError]] (assert (in "name 'b' is not defined" (str e)))))
(import itertools)
(setv foopermutations (fn [x] (itertools.permutations x)))
(setv p (set [(, 1 3 2) (, 3 2 1) (, 2 1 3) (, 3 1 2) (, 1 2 3) (, 2 3 1)]))
(assert-equal (set (itertools.permutations [1 2 3])) p)
(assert-equal (set (foopermutations [3 1 2])) p)
(setv permutations- itertools.permutations)
(setv itertools.permutations (fn [x] 9))
(assert-equal (itertools.permutations p) 9)
(assert-equal (foopermutations foopermutations) 9)
(setv itertools.permutations permutations-)
(assert-equal (set (itertools.permutations [2 1 3])) p)
(assert-equal (set (foopermutations [2 3 1])) p))
(defn test-distinct [] (defn test-distinct []
"NATIVE: testing the distinct function" "NATIVE: testing the distinct function"
(setv res (list (distinct [ 1 2 3 4 3 5 2 ]))) (setv res (list (distinct [ 1 2 3 4 3 5 2 ])))
@ -82,8 +110,8 @@
(assert-equal res [None 4 5]) (assert-equal res [None 4 5])
(setv res (list (drop 0 [1 2 3 4 5]))) (setv res (list (drop 0 [1 2 3 4 5])))
(assert-equal res [1 2 3 4 5]) (assert-equal res [1 2 3 4 5])
(setv res (list (drop -1 [1 2 3 4 5]))) (try (do (list (drop -1 [1 2 3 4 5])) (assert False))
(assert-equal res [1 2 3 4 5]) (catch [e [ValueError]] nil))
(setv res (list (drop 6 (iter [1 2 3 4 5])))) (setv res (list (drop 6 (iter [1 2 3 4 5]))))
(assert-equal res []) (assert-equal res [])
(setv res (list (take 5 (drop 2 (iterate inc 0))))) (setv res (list (take 5 (drop 2 (iterate inc 0)))))
@ -335,12 +363,15 @@
"NATIVE: testing the nth function" "NATIVE: testing the nth function"
(assert-equal 2 (nth [1 2 4 7] 1)) (assert-equal 2 (nth [1 2 4 7] 1))
(assert-equal 7 (nth [1 2 4 7] 3)) (assert-equal 7 (nth [1 2 4 7] 3))
(assert-true (none? (nth [1 2 4 7] 5))) (try (do (nth [1 2 4 7] 5) (assert False))
(assert-true (none? (nth [1 2 4 7] -1))) (catch [e [IndexError]] nil))
(try (do (nth [1 2 4 7] -1) (assert False))
(catch [e [ValueError]] nil))
;; now for iterators ;; now for iterators
(assert-equal 2 (nth (iter [1 2 4 7]) 1)) (assert-equal 2 (nth (iter [1 2 4 7]) 1))
(assert-equal 7 (nth (iter [1 2 4 7]) 3)) (assert-equal 7 (nth (iter [1 2 4 7]) 3))
(assert-true (none? (nth (iter [1 2 4 7]) -1))) (try (do (nth (iter [1 2 4 7]) -1) (assert False))
(catch [e [ValueError]] nil))
(assert-equal 5 (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2))) (assert-equal 5 (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2)))
(defn test-numeric? [] (defn test-numeric? []
@ -429,8 +460,8 @@
(assert-equal res ["s" "s" "s" "s"]) (assert-equal res ["s" "s" "s" "s"])
(setv res (list (take 0 (repeat "s")))) (setv res (list (take 0 (repeat "s"))))
(assert-equal res []) (assert-equal res [])
(setv res (list (take -1 (repeat "s")))) (try (do (list (take -1 (repeat "s"))) (assert False))
(assert-equal res []) (catch [e [ValueError]] nil))
(setv res (list (take 6 [1 2 None 4]))) (setv res (list (take 6 [1 2 None 4])))
(assert-equal res [1 2 None 4])) (assert-equal res [1 2 None 4]))
@ -478,3 +509,14 @@
(assert-equal (list res) [4 4 4]) (assert-equal (list res) [4 4 4])
(setv res (zipwith operator.sub [3 7 9] [1 2 4])) (setv res (zipwith operator.sub [3 7 9] [1 2 4]))
(assert-equal (list res) [2 5 5])) (assert-equal (list res) [2 5 5]))
(defn test-is-keyword []
"NATIVE: testing the keyword? function"
(assert (keyword? ':bar))
(assert (keyword? ':baz))
(assert (keyword? :bar))
(assert (keyword? :baz))
(assert (not (keyword? "foo")))
(assert (not (keyword? ":foo")))
(assert (not (keyword? 1)))
(assert (not (keyword? nil))))

View File

@ -29,7 +29,7 @@
(defn test-for-loop [] (defn test-for-loop []
"NATIVE: test for loops?" "NATIVE: test for loops"
(setv count 0) (setv count 0)
(for [x [1 2 3 4 5]] (for [x [1 2 3 4 5]]
(setv count (+ count x))) (setv count (+ count x)))
@ -38,7 +38,11 @@
(for [x [1 2 3 4 5] (for [x [1 2 3 4 5]
y [1 2 3 4 5]] y [1 2 3 4 5]]
(setv count (+ count x y))) (setv count (+ count x y)))
(assert (= count 150))) (assert (= count 150))
(assert (= (list ((fn [] (for [x [[1] [2 3]] y x] (yield y)))))
(list-comp y [x [[1] [2 3]] y x])))
(assert (= (list ((fn [] (for [x [[1] [2 3]] y x z (range 5)] (yield z)))))
(list-comp z [x [[1] [2 3]] y x z (range 5)]))))
(defn test-nasty-for-nesting [] (defn test-nasty-for-nesting []
@ -236,6 +240,11 @@
"NATIVE: test do" "NATIVE: test do"
(do)) (do))
(defn test-bare-try [] (try
(try (raise ValueError))
(except [ValueError])
(else (assert false))))
(defn test-exceptions [] (defn test-exceptions []
"NATIVE: test Exceptions" "NATIVE: test Exceptions"
@ -469,7 +478,7 @@
(defn test-rest [] (defn test-rest []
"NATIVE: test rest" "NATIVE: test rest"
(assert (= (rest [1 2 3 4 5]) [2 3 4 5]))) (assert (= (list (rest [1 2 3 4 5])) [2 3 4 5])))
(defn test-importas [] (defn test-importas []

View File

@ -0,0 +1,5 @@
(defmain [&rest args]
(print args)
(print "Hello World")
(if (in "exit1" args)
1))

View File

@ -0,0 +1,4 @@
(print "This Should Still Works")
(defn main []
(print "This Should Not Work"))

View File

@ -129,7 +129,7 @@ def test_hy2py():
for f in filenames: for f in filenames:
if f.endswith(".hy"): if f.endswith(".hy"):
i += 1 i += 1
ret = run_cmd("python bin/hy2py -s -a " ret = run_cmd("hy2py -s -a "
+ os.path.join(dirpath, f)) + os.path.join(dirpath, f))
assert ret[0] == 0, f assert ret[0] == 0, f
assert len(ret[1]) > 1, f assert len(ret[1]) > 1, f
@ -142,3 +142,27 @@ def test_bin_hy_builtins():
assert str(exit) == "Use (exit) or Ctrl-D (i.e. EOF) to exit" assert str(exit) == "Use (exit) or Ctrl-D (i.e. EOF) to exit"
assert str(quit) == "Use (quit) or Ctrl-D (i.e. EOF) to exit" assert str(quit) == "Use (quit) or Ctrl-D (i.e. EOF) to exit"
def test_bin_hy_main():
ret = run_cmd("hy tests/resources/bin/main.hy")
assert ret[0] == 0
assert "Hello World" in ret[1]
def test_bin_hy_main_args():
ret = run_cmd("hy tests/resources/bin/main.hy test 123")
assert ret[0] == 0
assert "test" in ret[1]
assert "123" in ret[1]
def test_bin_hy_main_exitvalue():
ret = run_cmd("hy tests/resources/bin/main.hy exit1")
assert ret[0] == 1
def test_bin_hy_no_main():
ret = run_cmd("hy tests/resources/bin/nomain.hy")
assert ret[0] == 0
assert "This Should Still Work" in ret[1]

37
tox.ini
View File

@ -1,39 +1,18 @@
[tox] [tox]
envlist = py27,pypy,py32,py33,py26,flake8 envlist = py26,py27,pypy,py32,py33,flake8
skipsdist = True
[testenv] [testenv]
commands = nosetests commands =
pip install --allow-all-external -e .
nosetests
deps = deps =
nose -rrequirements-dev.txt
setuptools
rply
[testenv:pypy]
commands = nosetests
deps =
astor
nose
setuptools
rply
[testenv:py27]
commands = nosetests
deps =
astor
nose
setuptools
rply
[testenv:py26] [testenv:py26]
deps = deps =
astor {[testenv]deps}
nose
setuptools
unittest2 unittest2
importlib
rply
[testenv:flake8] [testenv:flake8]
deps =
flake8
rply
commands = flake8 hy bin tests commands = flake8 hy bin tests