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
python:
- "pypy"
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "2.6"
- "3.4"
# command to install dependencies
install:
- pip install -r requirements.txt
- pip install -r requirements-dev.txt
- pip install coveralls
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse importlib unittest2 astor; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install astor; fi
- if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then pip install astor; fi
- python setup.py -q install
# # command to run tests
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi # needs for running tests
- pip install --allow-all-external -e .
# command to run tests
script: make travis
after_success: coveralls
notifications:

View File

@ -45,3 +45,5 @@
* kirbyfan64 <kirbyfan64@users.noreply.github.com>
* Brendan Curran-Johnson <brendan@bcjbcj.ca>
* 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
* hy2py supports a bunch of commandline options to show AST, source etc.
* Sub-object mangling: every identifier is split along the dots & mangled
seperately
separately
[ Bug Fixes ]
* Empty MacroExpansions work as expected
@ -88,7 +88,7 @@ Changes from Hy 0.9.11
what exactly was added.
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
and a massive hug goes out to olasd for providing ongoing
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
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)
* (decorate-with) has been moved to (with-decorate) (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
of bugs. (ND, PT, JD)
* 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)
@ -311,7 +311,7 @@ Changes from Hy 0.9.5
=============== WARNING: WARNING: READ ME: READ ME: ===================
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
amazing feautre.
amazing feature.
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)
* 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)
* Added "~" as the "invert" operator. (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
languge in a *core* way, but we never use them.
language in a *core* way, but we never use them.
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
# a list of builtin themes.
html_theme = 'default'
try:
import sphinx_rtd_theme
except ImportError:
html_theme = 'default'
else:
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the

View File

@ -89,26 +89,10 @@ To build the docs in HTML::
Write docs---docs are good! Even this doc!
Core Development Rules
======================
Pull requests are good!
Before you submit a PR, please run the tests and check your code against the style guide. You can do both these things at once::
$ make d
All incoming changes need to be acked by 2 different members of Hylang's
core team. Additional review is clearly welcome, but we need a minimum of
2 signoffs for any change.
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.
Contributing
============
.. include:: ../CONTRIBUTING.rst
Core Team
=========

View File

@ -271,6 +271,21 @@ however is called only for every other value in the list.
(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
----------
@ -441,6 +456,44 @@ symbols for function names as the first parameter, `defn-alias` and
=> (alias)
"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
@ -629,6 +682,24 @@ normally. If the execution is halted with `break`, the `else` does not execute.
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
@ -1003,6 +1074,22 @@ element:
[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
-----
@ -1057,7 +1144,7 @@ Example usage
; 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.
@ -1151,7 +1238,7 @@ while
`while` form is used to execute a single or more blocks as long as a condition
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
@ -1273,25 +1360,3 @@ yield-from
want your coroutine to be able to delegate its processes to another
coroutine, say if using something fancy like
`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
======================
.. _hy:
hy
--
@ -46,6 +48,8 @@ Command line options
Print the Hy version number and exit.
.. _hyc:
hyc
---
@ -71,3 +75,29 @@ Command line options
$ hyc hyname.hy
$ python hyname.pyc
Hy Afroman!
.. _hy2py:
hy2py
-----
.. versionadded:: 0.10.1
Command line options
^^^^^^^^^^^^^^^^^^^^
.. cmdoption:: -s
--with-source
Show the parsed source structure.
.. cmdoption:: -a
--with-ast
Show the generated AST.
.. cmdoption:: -np
--without-python
Do not show the Python code generated from the AST.

View File

@ -495,8 +495,8 @@ nth
Usage: ``(nth coll n)``
Return the `nth` item in a collection, counting from 0. Unlike
``get``, ``nth`` works on both iterators and iterables. Returns ``None``
if the `n` is outside the range of `coll`.
``get``, ``nth`` works on both iterators and iterables. Raises ``IndexError``
if the `n` is outside the range of ``coll`` or ``ValueError`` if it's negative.
.. code-block:: hy
@ -506,8 +506,10 @@ if the `n` is outside the range of `coll`.
=> (nth [1 2 4 7] 3)
7
=> (none? (nth [1 2 4 7] 5))
True
=> (nth [1 2 4 7] 5)
Traceback (most recent call last):
...
IndexError: 5
=> (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2))
5
@ -764,6 +766,7 @@ drop
Usage: ``(drop n coll)``
Return an iterator, skipping the first ``n`` members of ``coll``
Raises ``ValueError`` if ``n`` is negative.
.. code-block:: hy
@ -924,6 +927,7 @@ take
Usage: ``(take n coll)``
Return an iterator containing the first ``n`` members of ``coll``.
Raises ``ValueError`` if ``n`` is negative.
.. code-block:: hy
@ -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]))
[]
.. _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
expression is positive, zero or negative.
A first pass might be someting like:
A first pass might be something like:
.. code-block:: hy

View File

@ -70,4 +70,4 @@ and expression is passed to the correct function.
.. warning::
Because of a limitation in Hy's lexer and parser, reader macros can't
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))))
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

View File

@ -1,6 +1,6 @@
#!/usr/bin/env hy
;; 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.
(import sys)

View File

@ -23,7 +23,7 @@ import imp
import sys
# 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.
#

View File

@ -25,16 +25,21 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
from __future__ import print_function
import argparse
import code
import ast
import sys
import astor.codegen
import hy
from hy.lex import LexException, PrematureEndOfInput, tokenize
from hy.compiler import hy_compile, HyTypeError
from hy.importer import ast_compile, import_buffer_to_module
from hy.importer import (ast_compile, import_buffer_to_module,
import_file_to_ast, import_file_to_hst)
from hy.completer import completion
from hy.macros import macro, require
@ -66,7 +71,6 @@ builtins.exit = HyQuitter('exit')
def print_python_code(_ast):
import astor.codegen
# astor cannot handle ast.Interactive, so disguise it as a module
_ast_for_print = ast.Module()
_ast_for_print.body = _ast.body
@ -313,3 +317,43 @@ def hyc_main():
sys.stderr.write("hyc: Can't open file '%s': [Errno %d] %s\n" %
(x.filename, x.errno, x.strerror))
sys.exit(x.errno)
# entry point for cmd line script "hy2py"
def hy2py_main():
module_name = "<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)
handlers.append(handler_results.stmts.pop())
elif e[0] == HySymbol("else"):
if orelse:
raise HyTypeError(
e,
"`try' cannot have more than one `else'")
else:
orelse = self._compile_branch(e[1:])
# XXX tempvar magic
orelse += orelse.expr_as_stmt()
orelse = orelse.stmts
orelse = self.try_except_helper(e, HySymbol("else"), orelse)
elif e[0] == HySymbol("finally"):
if finalbody:
raise HyTypeError(
e,
"`try' cannot have more than one `finally'")
else:
finalbody = self._compile_branch(e[1:])
# XXX tempvar magic
finalbody += finalbody.expr_as_stmt()
finalbody = finalbody.stmts
finalbody = self.try_except_helper(e, HySymbol("finally"),
finalbody)
else:
raise HyTypeError(e, "Unknown expression in `try'")
@ -768,8 +753,8 @@ class HyASTCompiler(object):
col_offset=expr.start_column,
type=None,
name=None,
body=[ast.Pass(lineno=expr.start_line,
col_offset=expr.start_column)])]
body=[ast.Raise(lineno=expr.start_line,
col_offset=expr.start_column)])]
ret = handler_results
@ -809,6 +794,17 @@ class HyASTCompiler(object):
body=body,
orelse=orelse) + returnable
def try_except_helper(self, hy_obj, symbol, accumulated):
if accumulated:
raise HyTypeError(
hy_obj,
"`try' cannot have more than one `%s'" % symbol)
else:
accumulated = self._compile_branch(hy_obj[1:])
accumulated += accumulated.expr_as_stmt()
accumulated = accumulated.stmts
return accumulated
@builds("except")
@builds("catch")
def magic_internal_form(self, expr):
@ -1516,7 +1512,7 @@ class HyASTCompiler(object):
def compile_require(self, expression):
"""
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.
"""
expression.pop(0)
@ -1755,18 +1751,19 @@ class HyASTCompiler(object):
def _compile_assign(self, name, result,
start_line, start_column):
result = self.compile(result)
if result.temp_variables and isinstance(name, HyString):
result.rename(name)
return result
ld_name = self.compile(name)
st_name = self._storeize(ld_name)
result += ast.Assign(
lineno=start_line,
col_offset=start_column,
targets=[st_name], value=result.force_expr)
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(
lineno=start_line,
col_offset=start_column,
targets=[st_name],
value=result.force_expr)
result += ld_name
return result
@ -1859,7 +1856,7 @@ class HyASTCompiler(object):
ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist)
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.
args = [ast.arg(arg=ast_str(x),
annotation=None, # Fix me!

View File

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

View File

@ -31,34 +31,6 @@
[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]
"shorthand for nested for* loops:
(with [[x foo] [y bar]] baz) ->
@ -116,6 +88,26 @@
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]
;; TODO: fix the docstring by someone who understands this
(setv ret head)
@ -170,6 +162,21 @@
(let ~(HyList (map (fn [x] `[~x (gensym (slice '~x 2))]) syms))
~@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]
"define one function with several names"
(let [[main (first names)]

View File

@ -28,7 +28,7 @@ import traceback
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.
"""
pass

View File

@ -1,6 +1,12 @@
-r requirements.txt
tox
# test tools
nose
tox
# code quality
flake8
coverage
# documentation
Pygments>=1.6
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={
'console_scripts': [
'hy = hy.cmdline:hy_main',
'hyc = hy.cmdline:hyc_main'
'hyc = hy.cmdline:hyc_main',
'hy2py = hy.cmdline:hy2py_main',
]
},
packages=find_packages(exclude=['tests*']),

View File

@ -57,6 +57,34 @@
(try (do (dec None) (assert False))
(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 []
"NATIVE: testing the distinct function"
(setv res (list (distinct [ 1 2 3 4 3 5 2 ])))
@ -82,8 +110,8 @@
(assert-equal res [None 4 5])
(setv res (list (drop 0 [1 2 3 4 5])))
(assert-equal res [1 2 3 4 5])
(setv res (list (drop -1 [1 2 3 4 5])))
(assert-equal res [1 2 3 4 5])
(try (do (list (drop -1 [1 2 3 4 5])) (assert False))
(catch [e [ValueError]] nil))
(setv res (list (drop 6 (iter [1 2 3 4 5]))))
(assert-equal res [])
(setv res (list (take 5 (drop 2 (iterate inc 0)))))
@ -335,12 +363,15 @@
"NATIVE: testing the nth function"
(assert-equal 2 (nth [1 2 4 7] 1))
(assert-equal 7 (nth [1 2 4 7] 3))
(assert-true (none? (nth [1 2 4 7] 5)))
(assert-true (none? (nth [1 2 4 7] -1)))
(try (do (nth [1 2 4 7] 5) (assert False))
(catch [e [IndexError]] nil))
(try (do (nth [1 2 4 7] -1) (assert False))
(catch [e [ValueError]] nil))
;; now for iterators
(assert-equal 2 (nth (iter [1 2 4 7]) 1))
(assert-equal 7 (nth (iter [1 2 4 7]) 3))
(assert-true (none? (nth (iter [1 2 4 7]) -1)))
(try (do (nth (iter [1 2 4 7]) -1) (assert False))
(catch [e [ValueError]] nil))
(assert-equal 5 (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2)))
(defn test-numeric? []
@ -429,8 +460,8 @@
(assert-equal res ["s" "s" "s" "s"])
(setv res (list (take 0 (repeat "s"))))
(assert-equal res [])
(setv res (list (take -1 (repeat "s"))))
(assert-equal res [])
(try (do (list (take -1 (repeat "s"))) (assert False))
(catch [e [ValueError]] nil))
(setv res (list (take 6 [1 2 None 4])))
(assert-equal res [1 2 None 4]))
@ -478,3 +509,14 @@
(assert-equal (list res) [4 4 4])
(setv res (zipwith operator.sub [3 7 9] [1 2 4]))
(assert-equal (list res) [2 5 5]))
(defn test-is-keyword []
"NATIVE: testing the keyword? function"
(assert (keyword? ':bar))
(assert (keyword? ':baz))
(assert (keyword? :bar))
(assert (keyword? :baz))
(assert (not (keyword? "foo")))
(assert (not (keyword? ":foo")))
(assert (not (keyword? 1)))
(assert (not (keyword? nil))))

View File

@ -29,7 +29,7 @@
(defn test-for-loop []
"NATIVE: test for loops?"
"NATIVE: test for loops"
(setv count 0)
(for [x [1 2 3 4 5]]
(setv count (+ count x)))
@ -38,7 +38,11 @@
(for [x [1 2 3 4 5]
y [1 2 3 4 5]]
(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 []
@ -236,6 +240,11 @@
"NATIVE: test do"
(do))
(defn test-bare-try [] (try
(try (raise ValueError))
(except [ValueError])
(else (assert false))))
(defn test-exceptions []
"NATIVE: test Exceptions"
@ -469,7 +478,7 @@
(defn test-rest []
"NATIVE: test rest"
(assert (= (rest [1 2 3 4 5]) [2 3 4 5])))
(assert (= (list (rest [1 2 3 4 5])) [2 3 4 5])))
(defn test-importas []

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:
if f.endswith(".hy"):
i += 1
ret = run_cmd("python bin/hy2py -s -a "
ret = run_cmd("hy2py -s -a "
+ os.path.join(dirpath, f))
assert ret[0] == 0, f
assert len(ret[1]) > 1, f
@ -142,3 +142,27 @@ def test_bin_hy_builtins():
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"
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]
envlist = py27,pypy,py32,py33,py26,flake8
envlist = py26,py27,pypy,py32,py33,flake8
skipsdist = True
[testenv]
commands = nosetests
commands =
pip install --allow-all-external -e .
nosetests
deps =
nose
setuptools
rply
[testenv:pypy]
commands = nosetests
deps =
astor
nose
setuptools
rply
[testenv:py27]
commands = nosetests
deps =
astor
nose
setuptools
rply
-rrequirements-dev.txt
[testenv:py26]
deps =
astor
nose
setuptools
{[testenv]deps}
unittest2
importlib
rply
[testenv:flake8]
deps =
flake8
rply
commands = flake8 hy bin tests