Merge branch 'master' into multimethod

Conflicts:
	docs/contrib/multi.rst
This commit is contained in:
Tuukka Turto 2016-11-03 14:06:13 +02:00
commit 77bc767907
52 changed files with 599 additions and 204 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/hy/version.py
*.pyc
*swp
*hy*egg*

View File

@ -4,6 +4,8 @@ matrix:
include:
- python: 3.5
env: TOXENV=py35
- python: 3.6-dev
env: TOXENV=py36
env:
- TOXENV=py27
- TOXENV=py33

View File

@ -69,3 +69,4 @@
* Andrew Savchyn <dev@scorpil.com>
* Lev Kravinsky <kravinskylev@gmail.com>
* Luna Lunapiena <lunacodes@gmail.com>
* Jakub Wilk <jwilk@jwilk.net>

View File

@ -7,7 +7,9 @@ helps in making Hy more awesome.
Pull requests are great! We love them; here is a quick guide:
- `Fork the repo`_ and create a topic branch for a feature/fix. Avoid
making changes directly on the master branch.
making changes directly on the master branch. If you would like to
contribute but don't know how to begin, the `good-first-bug`_ label
of the `issue tracker`_ is the place to go.
(If you're new to Git: `Start Here`_)
- All incoming features should be accompanied with tests.
@ -36,7 +38,7 @@ Pull requests are great! We love them; here is a quick guide:
+ Try sticking to the 50 character limit for the first line of Git
commit messages.
+ For more detail/explainations, follow this up with a blank line and
+ For more detail/explanations, follow this up with a blank line and
continue describing the commit in detail.
- Finally, add yourself to the AUTHORS file (as a separate commit): you
@ -93,4 +95,5 @@ http://contributor-covenant.org/version/1/1/0/.
.. _Contributor Covenant: http://contributor-covenant.org
.. _issue tracker: https://github.com/hylang/hy/issues
.. _Fork the Repo: https://help.github.com/articles/fork-a-repo/
.. _Start Here: http://rogerdudler.github.io/git-guide/)
.. _Start Here: http://rogerdudler.github.io/git-guide/
.. _good-first-bug: http://github.com/hylang/hy/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-bug

View File

@ -23,7 +23,7 @@ all:
@echo ""
docs:
make -C docs html
$(MAKE) -C docs html
upload: r
python setup.py sdist upload
@ -72,10 +72,10 @@ coveralls:
clean:
@find . -name "*.pyc" -exec rm {} \;
@find -name __pycache__ -delete
@find . -name __pycache__ -delete
@${RM} -r -f .tox
@${RM} -r -f dist
@${RM} -r -f *.egg-info
@${RM} -r -f docs/_build
.PHONY: docs
.PHONY: all docs upload full venv dev test tox flake clear d diff r python coveralls clean

2
NEWS
View File

@ -280,7 +280,7 @@ Changes from Hy 0.9.10
* Clarified rest / cdr, cleaned up require
* Make with return the last expression from its branch
* Fix yielding to not suck (#151)
* Make assoc accept multiple values, also added a even/odd check for
* Make assoc accept multiple values, also added an even/odd check for
checkargs
* Added ability to parse doc strings set in defclass declarations,
* Provide bin scripts for both Windows and *nix

View File

@ -6,7 +6,7 @@ Hy
[![Version](https://img.shields.io/pypi/v/hy.svg)](https://pypi.python.org/pypi/hy)
[![Coverage Status](https://img.shields.io/coveralls/hylang/hy/master.svg)](https://coveralls.io/r/hylang/hy)
[![XKCD #224](https://raw.github.com/hylang/shyte/18f6925e08684b0e1f52b2cc2c803989cd62cd91/imgs/xkcd.png)](https://xkcd.com/224/)
<a href="https://xkcd.com/224/"><img title="We lost the documentation on quantum mechanics. You'll have to decode the regexes yourself." alt="XKCD #224" src="https://raw.github.com/hylang/shyte/18f6925e08684b0e1f52b2cc2c803989cd62cd91/imgs/xkcd.png"></a>
Lisp and Python should love each other. Let's make it happen. [Try it](http://try-hy.appspot.com/).

View File

@ -11,11 +11,11 @@ concise and easy to read.
deliberately captures some form supplied to the macro which may be
referred to by an anaphor (an expression referring to another).
-- Wikipedia (http://en.wikipedia.org/wiki/Anaphoric_macro)
-- Wikipedia (https://en.wikipedia.org/wiki/Anaphoric_macro)
To use these macros you need to require the hy.contrib.anaphoric module like so:
``(require hy.contrib.anaphoric)``
``(require [hy.contrib.anaphoric [*]])``
.. _ap-if:

View File

@ -25,7 +25,7 @@ Example:
.. code-block:: hy
(require hy.contrib.flow)
(require [hy.contrib.flow [case]])
(defn temp-commenter [temp]
(case temp
@ -48,7 +48,7 @@ Example:
.. code-block:: hy
(require hy.contrib.flow)
(require [hy.contrib.flow [switch]])
(defn temp-commenter [temp]
(switch temp

View File

@ -7,8 +7,9 @@ Contents:
.. toctree::
:maxdepth: 3
alias
anaphoric
flow
loop
multi
flow
alias
walk

View File

@ -24,7 +24,7 @@ tail-call optimization (TCO) in their Hy code.
position to be implemented as efficiently as goto statements, thus
allowing efficient structured programming.
-- Wikipedia (http://en.wikipedia.org/wiki/Tail_call)
-- Wikipedia (https://en.wikipedia.org/wiki/Tail_call)
Macros
======
@ -39,13 +39,13 @@ rebinds the variables set in the recursion point and sends code
execution back to that recursion point. If ``recur`` is used in a
non-tail position, an exception is raised.
Usage: `(loop bindings &rest body)`
Usage: ``(loop bindings &rest body)``
Example:
.. code-block:: hy
(require hy.contrib.loop)
(require [hy.contrib.loop [loop]])
(defn factorial [n]
(loop [[i n] [acc 1]]

View File

@ -2,7 +2,7 @@
defmulti
========
.. versionadded:: 0.10.0
.. versionadded:: 0.11.0
``defmulti``, ``defmethod`` and ``default-method`` lets you define
multimethods where a dispatching function is used to select between different
@ -11,7 +11,7 @@ on the code by `Adam Bard`_.
.. code-block:: clj
=> (require hy.contrib.multi)
=> (require [hy.contrib.multi [defmulti defmethod]])
=> (defmulti area [shape]
... "calculate area of a shape"
... (:type shape))
@ -59,7 +59,7 @@ different implementations can narrow them down.
.. code-block:: clj
=> (require hy.contrib.multi)
=> (require [hy.contrib.multi [defmulti defmethod]])
=> (defmulti fun [&rest args]
... (len args))

94
docs/contrib/walk.rst Normal file
View File

@ -0,0 +1,94 @@
====
walk
====
.. versionadded:: 0.11.0
Functions
=========
.. _walk:
walk
-----
Usage: `(walk inner outer form)`
``walk`` traverses ``form``, an arbitrary data structure. Applies
``inner`` to each element of form, building up a data structure of the
same type. Applies ``outer`` to the result.
Example:
.. code-block:: hy
=> (import [hy.contrib.walk [walk]])
=> (setv a '(a b c d e f))
=> (walk ord identity a)
(97 98 99 100 101 102)
=> (walk ord first a)
97
postwalk
---------
.. _postwalk:
Usage: `(postwalk f form)`
Performs depth-first, post-order traversal of ``form``. Calls ``f`` on
each sub-form, uses ``f`` 's return value in place of the original.
.. code-block:: hy
=> (import [hy.contrib.walk [postwalk]])
=> (def trail '([1 2 3] [4 [5 6 [7]]]))
=> (defn walking [x]
(print "Walking:" x)
x )
=> (postwalk walking trail)
Walking: 1
Walking: 2
Walking: 3
Walking: (1 2 3)
Walking: 4
Walking: 5
Walking: 6
Walking: 7
Walking: (7)
Walking: (5 6 [7])
Walking: (4 [5 6 [7]])
Walking: ([1 2 3] [4 [5 6 [7]]])
([1 2 3] [4 [5 6 [7]]])
prewalk
--------
.. _prewalk:
Usage: `(prewalk f form)`
Performs depth-first, pre-order traversal of ``form``. Calls ``f`` on
each sub-form, uses ``f`` 's return value in place of the original.
.. code-block:: hy
=> (import [hy.contrib.walk [prewalk]])
=> (def trail '([1 2 3] [4 [5 6 [7]]]))
=> (defn walking [x]
(print "Walking:" x)
x )
=> (prewalk walking trail)
Walking: ([1 2 3] [4 [5 6 [7]]])
Walking: [1 2 3]
Walking: 1
Walking: 2
Walking: 3
Walking: [4 [5 6 [7]]]
Walking: 4
Walking: [5 6 [7]]
Walking: 5
Walking: 6
Walking: [7]
Walking: 7
([1 2 3] [4 [5 6 [7]]])

View File

@ -32,7 +32,7 @@ Do this:
$ . venv/bin/activate
or use `virtualenvwrapper <http://virtualenvwrapper.readthedocs.org/en/latest/#introduction>`_
or use `virtualenvwrapper <https://virtualenvwrapper.readthedocs.io/en/latest/#introduction>`_
to create and manage your virtual environment::
$ mkvirtualenv hy
@ -63,7 +63,7 @@ Test!
=====
Tests are located in ``tests/``. We use `nose
<https://nose.readthedocs.org/en/latest/>`_.
<https://nose.readthedocs.io/en/latest/>`_.
To run the tests::

View File

@ -22,7 +22,7 @@ languages.
string. For example, ``foo`` will become ``FOO``.
* UTF-8 entities will be encoded using
`punycode <http://en.wikipedia.org/wiki/Punycode>`_ and prefixed with
`punycode <https://en.wikipedia.org/wiki/Punycode>`_ and prefixed with
``hy_``. For instance, ```` will become ``hy_w7h``, ```` will become
``hy_g6h``, and ``i♥u`` will become ``hy_iu_t0x``.
@ -179,6 +179,79 @@ other case, the first false value will be returned. Example usage:
False
as->
----
.. versionadded:: 0.12.0
Expands to sequence of assignments to the provided name, starting with head.
The previous result is thus available in the subsequent form. Returns the final
result, and leaves the name bound to it in the local scope. This behaves much
like the other threading macros, but requires you to specify the threading
point per form via the name instead of always the first or last argument.
.. code-block:: clj
;; example how -> and as-> relate
=> (as-> 0 it
... (inc it)
... (inc it))
2
=> (-> 0 inc inc)
2
;; create data for our cuttlefish database
=> (setv data [{:name "hooded cuttlefish"
... :classification {:subgenus "Acanthosepion"
... :species "Sepia prashadi"}
... :discovered {:year 1936
... :name "Ronald Winckworth"}}
... {:name "slender cuttlefish"
... :classification {:subgenus "Doratosepion"
... :species "Sepia braggi"}
... :discovered {:year 1907
... :name "Sir Joseph Cooke Verco"}}])
;; retrieve name of first entry
=> (as-> (first data) it
... (:name it))
'hooded cuttlefish'
;; retrieve species of first entry
=> (as-> (first data) it
... (:classification it)
... (:species it))
'Sepia prashadi'
;; find out who discovered slender cuttlefish
=> (as-> (filter (fn [entry] (= (:name entry)
... "slender cuttlefish")) data) it
... (first it)
... (:discovered it)
... (:name it))
'Sir Joseph Cooke Verco'
;; more convoluted example to load web page and retrieve data from it
=> (import [urllib.request [urlopen]])
=> (as-> (urlopen "http://docs.hylang.org/en/stable/") it
... (.read it)
... (.decode it "utf-8")
... (drop (.index it "Welcome") it)
... (take 30 it)
... (list it)
... (.join "" it))
'Welcome to Hys documentation!
.. note::
In these examples, the REPL will report a tuple (e.g. `('Sepia prashadi',
'Sepia prashadi')`) as the result, but only a single value is actually
returned.
assert
------
@ -953,11 +1026,20 @@ that ``import`` can be used.
(import [sys :as systest])
;; You can list as many imports as you like of different types.
;;
;; Python:
;; from tests.resources import kwtest, function_with_a_dash
;; from os.path import exists, isdir as is_dir, isfile as is_file
;; import sys as systest
(import [tests.resources [kwtest function-with-a-dash]]
[os.path [exists isdir isfile]]
[os.path [exists
isdir :as dir?
isfile :as file?]]
[sys :as systest])
;; Import all module functions into current namespace
;;
;; Python: from sys import *
(import [sys [*]])
@ -1201,16 +1283,84 @@ alternatively be written using the apostrophe (``'``) symbol.
require
-------
``require`` is used to import macros from a given module. It takes at least one
parameter specifying the module which macros should be imported. Multiple
modules can be imported with a single ``require``.
``require`` is used to import macros from one or more given modules. It allows
parameters in all the same formats as ``import``. The ``require`` form itself
produces no code in the final program: its effect is purely at compile-time, for
the benefit of macro expansion. Specifically, ``require`` imports each named
module and then makes each requested macro available in the current module.
The following example will import macros from ``module-1`` and ``module-2``:
The following are all equivalent ways to call a macro named ``foo`` in the module ``mymodule``:
.. code-block:: clj
(require module-1 module-2)
(require mymodule)
(mymodule.foo 1)
(require [mymodule :as M])
(M.foo 1)
(require [mymodule [foo]])
(foo 1)
(require [mymodule [*]])
(foo 1)
(require [mymodule [foo :as bar]])
(bar 1)
Macros that call macros
~~~~~~~~~~~~~~~~~~~~~~~
One aspect of ``require`` that may be surprising is what happens when one
macro's expansion calls another macro. Suppose ``mymodule.hy`` looks like this:
.. code-block:: clj
(defmacro repexpr [n expr]
; Evaluate the expression n times
; and collect the results in a list.
`(list (map (fn [_] ~expr) (range ~n))))
(defmacro foo [n]
`(repexpr ~n (input "Gimme some input: ")))
And then, in your main program, you write:
.. code-block:: clj
(require [mymodule [foo]])
(print (mymodule.foo 3))
Running this raises ``NameError: name 'repexpr' is not defined``, even though
writing ``(print (foo 3))`` in ``mymodule`` works fine. The trouble is that your
main program doesn't have the macro ``repexpr`` available, since it wasn't
imported (and imported under exactly that name, as opposed to a qualified name).
You could do ``(require [mymodule [*]])`` or ``(require [mymodule [foo
repexpr]])``, but a less error-prone approach is to change the definition of
``foo`` to require whatever sub-macros it needs:
.. code-block:: clj
(defmacro foo [n]
`(do
(require mymodule)
(mymodule.repexpr ~n (raw-input "Gimme some input: "))))
It's wise to use ``(require mymodule)`` here rather than ``(require [mymodule
[repexpr]])`` to avoid accidentally shadowing a function named ``repexpr`` in
the main program.
Qualified macro names
~~~~~~~~~~~~~~~~~~~~~
Note that in the current implementation, there's a trick in qualified macro
names, like ``mymodule.foo`` and ``M.foo`` in the above example. These names
aren't actually attributes of module objects; they're just identifiers with
periods in them. In fact, ``mymodule`` and ``M`` aren't defined by these
``require`` forms, even at compile-time. None of this will hurt you unless try
to do introspection of the current module's set of defined macros, which isn't
really supported anyway.
rest / cdr
----------

View File

@ -35,7 +35,7 @@ Command Line Options
.. cmdoption:: --spy
Print equivalent Python code before executing. For example::
Print equivalent Python code before executing in REPL. For example::
=> (defn salutationsnm [name] (print (+ "Hy " name "!")))
def salutationsnm(name):
@ -45,6 +45,7 @@ Command Line Options
Hy YourName!
=>
`--spy` only works on REPL mode.
.. versionadded:: 0.9.11
.. cmdoption:: --show-tracebacks

View File

@ -141,7 +141,7 @@ is ``True``, the function prints Python code instead.
print('Hello World!')
.. _emtpy?-fn:
.. _empty?-fn:
empty?
------

View File

@ -170,7 +170,7 @@ Cons Cells
cells`_. Cons cells are especially useful to mimic features of "usual"
LISP variants such as Scheme or Common Lisp.
.. _cons cells: http://en.wikipedia.org/wiki/Cons
.. _cons cells: https://en.wikipedia.org/wiki/Cons
A cons cell is a 2-item object, containing a ``car`` (head) and a
``cdr`` (tail). In some Lisp variants, the cons cell is the fundamental

View File

@ -13,7 +13,7 @@ Quickstart
1. Create a `Virtual Python Environment
<https://pypi.python.org/pypi/virtualenv>`_.
2. Activate your Virtual Python Environment.
3. Install `hy from PyPI <https://pypi.python.org/pypi/hy>`_ with ``pip install hy``.
3. Install `hy from GitHub <https://github.com/hylang/hy>`_ with ``$ pip install git+https://github.com/hylang/hy.git``.
4. Start a REPL with ``hy``.
5. Type stuff in the REPL::

View File

@ -105,7 +105,7 @@ Layout & Indentation
.. code-block:: clj
(let [foo (bar)]
(let [foo (bar)
qux (baz)]
(foo qux))

View File

@ -267,7 +267,7 @@ In Hy, you would do:
[true
(print "That variable is jussssst right!")])
What you'll notice is that ``cond`` switches off between a some statement
What you'll notice is that ``cond`` switches off between a statement
that is executed and checked conditionally for true or falseness, and
then a bit of code to execute if it turns out to be true. You'll also
notice that the ``else`` is implemented at the end simply by checking
@ -292,7 +292,7 @@ You can do the following:
(if (try-some-thing)
(do
(print "this is if true")
(print "and why not, let's keep talking about how true it is!))
(print "and why not, let's keep talking about how true it is!"))
(print "this one's still simply just false"))
You can see that we used ``do`` to wrap multiple statements. If you're
@ -568,15 +568,18 @@ Macros are useful when one wishes to extend Hy or write their own
language on top of that. Many features of Hy are macros, like ``when``,
``cond`` and ``->``.
To use macros defined in a different module, it is not enough to
``import`` the module, because importing happens at run-time, while we
would need macros at compile-time. Instead of importing the module
with macros, ``require`` must be used:
What if you want to use a macro that's defined in a different
module? The special form ``import`` won't help, because it merely
translates to a Python ``import`` statement that's executed at
run-time, and macros are expanded at compile-time, that is,
during the translate from Hy to Python. Instead, use ``require``,
which imports the module and makes macros available at
compile-time. ``require`` uses the same syntax as ``import``.
.. code-block:: clj
=> (require tutorial.macros)
=> (rev (1 2 3 +))
=> (tutorial.macros.rev (1 2 3 +))
6
Hy <-> Python interop

View File

@ -19,7 +19,11 @@
# DEALINGS IN THE SOFTWARE.
from hy.version import __version__, __appname__ # NOQA
__appname__ = 'hy'
try:
from hy.version import __version__
except ImportError:
__version__ = 'unknown'
from hy.models.expression import HyExpression # NOQA

View File

@ -173,8 +173,8 @@ def ideas_macro():
""")])
require("hy.cmdline", "__console__")
require("hy.cmdline", "__main__")
require("hy.cmdline", "__console__", all_macros=True)
require("hy.cmdline", "__main__", all_macros=True)
SIMPLE_TRACEBACKS = True
@ -274,7 +274,7 @@ def cmdline_handler(scriptname, argv):
parser.add_argument("--spy", action="store_true",
help="print equivalent Python code before executing")
parser.add_argument("-v", action="version", version=VERSION)
parser.add_argument("-v", "--version", action="version", version=VERSION)
parser.add_argument("--show-tracebacks", action="store_true",
help="show complete tracebacks for Hy exceptions")

View File

@ -503,19 +503,13 @@ class HyASTCompiler(object):
for expr in exprs:
if expr in ll_keywords:
if expr == "&rest" and lambda_keyword is None:
lambda_keyword = expr
elif expr == "&optional":
if expr == "&optional":
if len(defaults) > 0:
raise HyTypeError(expr,
"There can only be &optional "
"arguments or one &key argument")
lambda_keyword = expr
elif expr == "&key":
lambda_keyword = expr
elif expr == "&kwonly":
lambda_keyword = expr
elif expr == "&kwargs":
elif expr in ("&rest", "&key", "&kwonly", "&kwargs"):
lambda_keyword = expr
else:
raise HyTypeError(expr,
@ -1516,7 +1510,8 @@ class HyASTCompiler(object):
gen.append(ast.comprehension(
target=target,
iter=gen_res.force_expr,
ifs=[]))
ifs=[],
is_async=False))
if cond.expr:
gen[-1].ifs.append(cond.expr)
@ -1742,10 +1737,45 @@ class HyASTCompiler(object):
"unimport" it after we've completed `thing' so that we don't pollute
other envs.
"""
expression.pop(0)
for entry in expression:
__import__(entry) # Import it fo' them macros.
require(entry, self.module_name)
for entry in expression[1:]:
if isinstance(entry, HySymbol):
# e.g., (require foo)
__import__(entry)
require(entry, self.module_name, all_macros=True,
prefix=entry)
elif isinstance(entry, HyList) and len(entry) == 2:
# e.g., (require [foo [bar baz :as MyBaz bing]])
# or (require [foo [*]])
module, names = entry
if not isinstance(names, HyList):
raise HyTypeError(names,
"(require) name lists should be HyLists")
__import__(module)
if '*' in names:
if len(names) != 1:
raise HyTypeError(names, "* in a (require) name list "
"must be on its own")
require(module, self.module_name, all_macros=True)
else:
assignments = {}
while names:
if len(names) > 1 and names[1] == HyKeyword(":as"):
k, _, v = names[:3]
del names[:3]
assignments[k] = v
else:
symbol = names.pop(0)
assignments[symbol] = symbol
require(module, self.module_name, assignments=assignments)
elif (isinstance(entry, HyList) and len(entry) == 3
and entry[1] == HyKeyword(":as")):
# e.g., (require [foo :as bar])
module, _, prefix = entry
__import__(module)
require(module, self.module_name, all_macros=True,
prefix=prefix)
else:
raise HyTypeError(entry, "unrecognized (require) syntax")
return Result()
@builds("and")
@ -1910,7 +1940,6 @@ class HyASTCompiler(object):
col_offset=child.start_column)
return ret
@builds("+")
@builds("*")
@builds("/")
@builds("//")
@ -1918,8 +1947,7 @@ class HyASTCompiler(object):
if len(expression) > 2:
return self.compile_maths_expression(expression)
else:
id_op = {"+": HyInteger(0), "*": HyInteger(1), "/": HyInteger(1),
"//": HyInteger(1)}
id_op = {"*": HyInteger(1), "/": HyInteger(1), "//": HyInteger(1)}
op = expression.pop(0)
arg = expression.pop(0) if expression else id_op[op]
@ -1930,20 +1958,34 @@ class HyASTCompiler(object):
]).replace(expression)
return self.compile_maths_expression(expr)
@builds("-")
@checkargs(min=1)
def compile_maths_expression_sub(self, expression):
def compile_maths_expression_additive(self, expression):
if len(expression) > 2:
return self.compile_maths_expression(expression)
else:
arg = expression[1]
op = {"+": ast.UAdd, "-": ast.USub}[expression.pop(0)]()
arg = expression.pop(0)
ret = self.compile(arg)
ret += ast.UnaryOp(op=ast.USub(),
ret += ast.UnaryOp(op=op,
operand=ret.force_expr,
lineno=arg.start_line,
col_offset=arg.start_column)
return ret
@builds("+")
def compile_maths_expression_add(self, expression):
if len(expression) == 1:
# Nullary +
return ast.Num(n=long_type(0),
lineno=expression.start_line,
col_offset=expression.start_column)
else:
return self.compile_maths_expression_additive(expression)
@builds("-")
@checkargs(min=1)
def compile_maths_expression_sub(self, expression):
return self.compile_maths_expression_additive(expression)
@builds("+=")
@builds("/=")
@builds("//=")
@ -2449,6 +2491,9 @@ class HyASTCompiler(object):
raise HyTypeError(name, ("received a `%s' instead of a symbol "
"for macro name" % type(name).__name__))
name = HyString(name).replace(name)
for kw in ("&kwonly", "&kwargs", "&key"):
if kw in expression[0]:
raise HyTypeError(name, "macros cannot use %s" % kw)
new_expression = HyExpression([
HySymbol("with_decorator"),
HyExpression([HySymbol("hy.macros.macro"), name]),

View File

@ -17,4 +17,5 @@
(defmacro defnc [name args &rest body]
`(def ~name (fnc [~@args] ~@body)))
`(do (require hy.contrib.curry)
(def ~name (hy.contrib.curry.fnc [~@args] ~@body))))

View File

@ -70,7 +70,8 @@
(defmacro defnr [name lambda-list &rest body]
(if (not (= (type name) HySymbol))
(macro-error name "defnr takes a name as first argument"))
`(setv ~name (fnr ~lambda-list ~@body)))
`(do (require hy.contrib.loop)
(setv ~name (hy.contrib.loop.fnr ~lambda-list ~@body))))
(defmacro/g! loop [bindings &rest body]
@ -87,5 +88,6 @@
;; and erroring if not is a giant TODO.
(let [fnargs (map (fn [x] (first x)) bindings)
initargs (map second bindings)]
`(do (defnr ~g!recur-fn [~@fnargs] ~@body)
`(do (require hy.contrib.loop)
(hy.contrib.loop.defnr ~g!recur-fn [~@fnargs] ~@body)
(~g!recur-fn ~@initargs))))

View File

@ -9,19 +9,23 @@
(defn ~name ~params
(do ~@code)))))
(defn rwm [name path method params code]
`(do (require hy.contrib.meth)
(hy.contrib.meth.route-with-methods ~name ~path ~method ~params ~@code)))
;; Some macro examples
(defmacro route [name path params &rest code]
"Get request"
`(route-with-methods ~name ~path ["GET"] ~params ~@code))
(rwm name path ["GET"] params code))
(defmacro post-route [name path params &rest code]
"Post request"
`(route-with-methods ~name ~path ["POST"] ~params ~@code))
(rwm name path ["POST"] params code))
(defmacro put-route [name path params &rest code]
"Put request"
`(route-with-methods ~name ~path ["PUT"] ~params ~@code))
(rwm name path ["PUT"] params code))
(defmacro delete-route [name path params &rest code]
"Delete request"
`(route-with-methods ~name ~path ["DELETE"] ~params ~@code))
(rwm name path ["DELETE"] params code))

View File

@ -39,10 +39,6 @@
(import [hy.lex [LexException PrematureEndOfInput tokenize]])
(import [hy.compiler [HyASTCompiler]])
(defn _numeric-check [x]
(if (not (numeric? x))
(raise (TypeError (.format "{0!r} is not a number" x)))))
(defn butlast [coll]
"Returns coll except of last element."
(drop-last 1 coll))
@ -66,7 +62,6 @@
(defn dec [n]
"Decrement n by 1"
(_numeric-check n)
(- n 1))
(defn disassemble [tree &optional [codegen false]]
@ -170,7 +165,6 @@
(defn even? [n]
"Return true if n is an even number"
(_numeric-check n)
(= (% n 2) 0))
(defn every? [pred coll]
@ -239,7 +233,6 @@
(defn inc [n]
"Increment n by 1"
(_numeric-check n)
(+ n 1))
(defn instance? [klass x]
@ -325,7 +318,6 @@
(defn neg? [n]
"Return true if n is < 0"
(_numeric-check n)
(< n 0))
(defn none? [x]
@ -347,7 +339,6 @@
(defn odd? [n]
"Return true if n is an odd number"
(_numeric-check n)
(= (% n 2) 1))
(def -sentinel (object))
@ -364,7 +355,6 @@
(defn pos? [n]
"Return true if n is > 0"
(_numeric_check n)
(> n 0))
(defn rest [coll]
@ -415,7 +405,6 @@
(defn zero? [n]
"Return true if n is 0"
(_numeric_check n)
(= n 0))
(defn read [&optional [from-file sys.stdin]

View File

@ -30,6 +30,16 @@
[hy.models.symbol [HySymbol]]
[hy._compat [PY33 PY34]])
(defmacro as-> [head name &rest rest]
"Expands to sequence of assignments to the provided name, starting with head.
The previous result is thus available in the subsequent form. Returns the
final result, and leaves the name bound to it in the local scope. This behaves
much like the other threading macros, but requires you to specify the threading
point per form via the name instead of always the first or last argument."
`(do (setv
~name ~head
~@(interleave (repeat name) rest))
~name))
(defmacro with [args &rest body]
"shorthand for nested with* loops:

View File

@ -30,7 +30,7 @@
(if (zero? count)
(raise (TypeError "Need at least 1 argument to add/concatenate"))
(if (= count 1)
(get args 0)
(operator.pos (get args 0))
(reduce operator.add args)))))

View File

@ -45,12 +45,12 @@ def ast_compile(ast, filename, mode):
def import_buffer_to_hst(buf):
"""Import content from buf and return an Hy AST."""
"""Import content from buf and return a Hy AST."""
return tokenize(buf + "\n")
def import_file_to_hst(fpath):
"""Import content from fpath and return an Hy AST."""
"""Import content from fpath and return a Hy AST."""
try:
with open(fpath, 'r', encoding='utf-8') as f:
return import_buffer_to_hst(f.read())
@ -89,6 +89,15 @@ def import_file_to_module(module_name, fpath):
return mod
def import_file_to_globals(env, module_name, fpath):
""" Import content from fpath and puts it into the dict provided
(e.g., for use in a REPL)
"""
mod = import_file_to_module(module_name, fpath)
for k, v in mod.__dict__.items():
env[k] = v
def import_buffer_to_module(module_name, buf):
try:
_ast = import_buffer_to_ast(buf, module_name)

View File

@ -84,22 +84,36 @@ def reader(name):
return _
def require(source_module, target_module):
"""Load the macros from `source_module` in the namespace of
`target_module`.
def require(source_module, target_module,
all_macros=False, assignments={}, prefix=""):
"""Load macros from `source_module` in the namespace of
`target_module`. `assignments` maps old names to new names, but is
ignored if `all_macros` is true. If `prefix` is nonempty, it is
prepended to the name of each imported macro. (This means you get
macros named things like "mymacromodule.mymacro", which looks like
an attribute of a module, although it's actually just a symbol
with a period in its name.)
This function is called from the `require` special form in the compiler.
"""
macros = _hy_macros[source_module]
refs = _hy_macros[target_module]
for name, macro in macros.items():
refs[name] = macro
readers = _hy_reader[source_module]
reader_refs = _hy_reader[target_module]
for name, reader in readers.items():
reader_refs[name] = reader
seen_names = set()
if prefix:
prefix += "."
for d in _hy_macros, _hy_reader:
for name, macro in d[source_module].items():
seen_names.add(name)
if all_macros:
d[target_module][prefix + name] = macro
elif name in assignments:
d[target_module][prefix + assignments[name]] = macro
if not all_macros:
unseen = frozenset(assignments.keys()).difference(seen_names)
if unseen:
raise ImportError("cannot require names: " + repr(list(unseen)))
def load_macros(module_name):

View File

@ -18,19 +18,16 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
from hy.models import _wrappers, wrap_value
from hy.models.list import HyList
from functools import reduce
class HySet(HyList):
"""
Hy set (actually a list that pretends to be a set)
Hy set (just a representation of a set)
"""
def __init__(self, items):
items = sorted(items)
items = list(reduce(lambda r, v: v in r and r or r+[v], items, []))
super(HySet, self).__init__(items)
def __repr__(self):
return "#{%s}" % (" ".join([repr(x) for x in self]))
_wrappers[set] = lambda s: HySet(wrap_value(x) for x in s)

View File

@ -1,23 +0,0 @@
# Copyright (c) 2013 Paul Tagliamonte <paultag@debian.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
__appname__ = "hy"
__version__ = "0.11.0"

View File

@ -22,24 +22,23 @@
import os
import re
import sys
import subprocess
from setuptools import find_packages, setup
os.chdir(os.path.split(os.path.abspath(__file__))[0])
PKG = "hy"
VERSIONFILE = os.path.join(PKG, "version.py")
verstr = "unknown"
try:
verstrline = open(VERSIONFILE, "rt").read()
except EnvironmentError:
pass # Okay, there is no version file.
else:
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
__version__ = mo.group(1)
else:
msg = "if %s.py exists, it is required to be well-formed" % VERSIONFILE
raise RuntimeError(msg)
__version__ = (subprocess.check_output
(["git", "describe", "--tags", "--dirty"])
.decode('ASCII').strip()
.replace('-', '+', 1).replace('-', '.'))
with open(VERSIONFILE, "wt") as o:
o.write("__version__ = {!r}\n".format(__version__))
except subprocess.CalledProcessError:
__version__ = "unknown"
long_description = """Hy is a Python <--> Lisp layer. It helps
make things work nicer, and lets Python and the Hy lisp variant play

View File

@ -249,6 +249,20 @@ def test_ast_good_import_from():
can_compile("(import [x [y]])")
def test_ast_require():
"Make sure AST respects (require) syntax"
can_compile("(require tests.resources.tlib)")
can_compile("(require [tests.resources.tlib [qplah parald]])")
can_compile("(require [tests.resources.tlib [*]])")
can_compile("(require [tests.resources.tlib :as foobar])")
can_compile("(require [tests.resources.tlib [qplah :as quiz]])")
can_compile("(require [tests.resources.tlib [qplah :as quiz parald]])")
cant_compile("(require [tests.resources.tlib])")
cant_compile("(require [tests.resources.tlib [* qplah]])")
cant_compile("(require [tests.resources.tlib [qplah *]])")
cant_compile("(require [tests.resources.tlib [* *]])")
def test_ast_good_get():
"Make sure AST can compile valid get"
can_compile("(get x y)")
@ -397,6 +411,7 @@ def test_lambda_list_keywords_rest():
""" Ensure we can compile functions with lambda list keywords."""
can_compile("(fn (x &rest xs) (print xs))")
cant_compile("(fn (x &rest xs &rest ys) (print xs))")
can_compile("(fn (&optional a &rest xs) (print xs))")
def test_lambda_list_keywords_key():
@ -410,6 +425,7 @@ def test_lambda_list_keywords_kwargs():
""" Ensure we can compile functions with &kwargs."""
can_compile("(fn (x &kwargs kw) (list x kw))")
cant_compile("(fn (x &kwargs xs &kwargs ys) (list x xs ys))")
can_compile("(fn (&optional x &kwargs kw) (list x kw))")
def test_lambda_list_keywords_kwonly():

View File

@ -230,6 +230,16 @@ def test_sets():
HyExpression([HySymbol("baz"), HySymbol("quux")])
])]
# Duplicate items in a literal set should be okay (and should
# be preserved).
objs = tokenize("#{1 2 1 1 2 1}")
assert objs == [HySet([HyInteger(n) for n in [1, 2, 1, 1, 2, 1]])]
assert len(objs[0]) == 6
# https://github.com/hylang/hy/issues/1120
objs = tokenize("#{a 1}")
assert objs == [HySet([HySymbol("a"), HyInteger(1)])]
def test_nospace():
""" Ensure we can tokenize without spaces if we have to """

View File

@ -5,4 +5,4 @@ hyset = HySet([3, 1, 2, 2])
def test_set():
assert hyset == [1, 2, 3]
assert hyset == [3, 1, 2, 2]

View File

@ -1,4 +1,4 @@
(require hy.contrib.alias)
(require [hy.contrib.alias [defn-alias]])
(defn test-defn-alias []
(defn-alias [tda-main tda-a1 tda-a2] [] :bazinga)

View File

@ -19,7 +19,7 @@
;; DEALINGS IN THE SOFTWARE.
(import [hy.errors [HyMacroExpansionError]])
(require hy.contrib.anaphoric)
(require [hy.contrib.anaphoric [*]])
;;;; some simple helpers

View File

@ -1,5 +1,4 @@
(import [hy.contrib.botsbuildbots [*]])
(require hy.contrib.botsbuildbots)
(require [hy.contrib.botsbuildbots [Botsbuildbots]])
(defn test-botsbuildbots []
(assert (> (len (first (Botsbuildbots))) 50)))

View File

@ -1,4 +1,4 @@
(require hy.contrib.curry)
(require [hy.contrib.curry [defnc]])
(defnc s [x y z] ((x z) (y z))) ; λxyz.xz(yz)

View File

@ -1,4 +1,4 @@
(require hy.contrib.loop)
(require [hy.contrib.loop [loop]])
(import sys)
(defn tco-sum [x y]

View File

@ -1,4 +1,4 @@
(require hy.contrib.meth)
(require [hy.contrib.meth [route post-route put-route delete-route]])
(defclass FakeMeth []
"Mocking decorator class"

View File

@ -19,7 +19,7 @@
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;; DEALINGS IN THE SOFTWARE.
(require hy.contrib.multi)
(require [hy.contrib.multi [defmulti]])
(defn test-different-signatures []

View File

@ -19,6 +19,8 @@
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;; DEALINGS IN THE SOFTWARE.
(import [hy._compat [PY3]])
;;;; some simple helpers
(defn assert-true [x]
@ -33,6 +35,12 @@
(defn assert-nil [x]
(assert (is x nil)))
(defn assert-requires-num [f]
(for [x ["foo" [] None]]
(try (f x)
(except [TypeError] True)
(else (assert False)))))
(defn test-coll? []
"NATIVE: testing coll?"
(assert-true (coll? [1 2 3]))
@ -66,12 +74,7 @@
(assert-equal 0 (dec 1))
(assert-equal -1 (dec 0))
(assert-equal 0 (dec (dec 2)))
(try (do (dec "foo") (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (dec []) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (dec None) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e))))))
(assert-requires-num dec))
(defn test-setv []
"NATIVE: testing setv mutation"
@ -173,12 +176,7 @@
(assert-true (even? -2))
(assert-false (even? 1))
(assert-true (even? 0))
(try (even? "foo")
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (even? [])
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (even? None)
(except [e [TypeError]] (assert (in "not a number" (str e))))))
(assert-requires-num even?))
(defn test-every? []
"NATIVE: testing the every? function"
@ -263,12 +261,11 @@
"NATIVE: testing the inc function"
(assert-equal 3 (inc 2))
(assert-equal 0 (inc -1))
(try (do (inc "foo") (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (inc []) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (inc None) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e))))))
(assert-requires-num inc)
(defclass X [object]
[__add__ (fn [self other] (.format "__add__ got {}" other))])
(assert-equal (inc (X)) "__add__ got 1"))
(defn test-instance []
"NATIVE: testing instance? function"
@ -394,24 +391,14 @@
(assert-true (neg? -2))
(assert-false (neg? 1))
(assert-false (neg? 0))
(try (do (neg? "foo") (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (neg? []) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (neg? None) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e))))))
(when PY3
(assert-requires-num neg?)))
(defn test-zero []
"NATIVE: testing the zero? function"
(assert-false (zero? -2))
(assert-false (zero? 1))
(assert-true (zero? 0))
(try (do (zero? "foo") (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (zero? []) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (zero? None) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e))))))
(assert-true (zero? 0)))
(defn test-none []
"NATIVE: testing for `is None`"
@ -463,12 +450,7 @@
(assert-true (odd? -3))
(assert-true (odd? 1))
(assert-false (odd? 0))
(try (do (odd? "foo") (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (odd? []) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (odd? None) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e))))))
(assert-requires-num odd?))
(defn test-partition []
"NATIVE: testing the partition function"
@ -500,12 +482,8 @@
(assert-true (pos? 2))
(assert-false (pos? -1))
(assert-false (pos? 0))
(try (do (pos? "foo") (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (pos? []) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e)))))
(try (do (pos? None) (assert False))
(except [e [TypeError]] (assert (in "not a number" (str e))))))
(when PY3
(assert-requires-num pos?)))
(defn test-remove []
"NATIVE: testing the remove function"

View File

@ -47,6 +47,7 @@
(defn test-sets []
"NATIVE: test sets work right"
(assert (= #{1 2 3 4} (| #{1 2} #{3 4})))
(assert (= (type #{1 2 3 4}) set))
(assert (= #{} (set))))
@ -587,6 +588,29 @@
["X" "B" "C" "D"])))
(defn test-as-threading []
"NATIVE: test as threading macro"
(setv data [{:name "hooded cuttlefish"
:classification {:subgenus "Acanthosepion"
:species "Sepia prashadi"}
:discovered {:year 1936
:name "Ronald Winckworth"}}
{:name "slender cuttlefish"
:classification {:subgenus "Doratosepion"
:species "Sepia braggi"}
:discovered {:year 1907
:name "Sir Joseph Cooke Verco"}}])
(assert (= (as-> (first data) x
(:name x))
"hooded cuttlefish"))
(assert (= (as-> (filter (fn [entry] (= (:name entry)
"slender cuttlefish")) data) x
(first x)
(:discovered x)
(:name x))
"Sir Joseph Cooke Verco")))
(defn test-assoc []
"NATIVE: test assoc"
(setv vals {"one" "two"})
@ -1100,11 +1124,34 @@
(defn test-require []
"NATIVE: test requiring macros from python code"
(try
(assert (= "this won't happen" (qplah 1 2 3 4)))
(except [NameError]))
(try (qplah 1 2 3 4)
(except [NameError] True)
(else (assert False)))
(try (parald 1 2 3 4)
(except [NameError] True)
(else (assert False)))
(require [tests.resources.tlib [qplah]])
(assert (= (qplah 1 2 3) [8 1 2 3]))
(try (parald 1 2 3 4)
(except [NameError] True)
(else (assert False)))
(require tests.resources.tlib)
(assert (= [1 2 3] (qplah 1 2 3))))
(assert (= (tests.resources.tlib.parald 1 2 3) [9 1 2 3]))
(try (parald 1 2 3 4)
(except [NameError] True)
(else (assert False)))
(require [tests.resources.tlib :as T])
(assert (= (T.parald 1 2 3) [9 1 2 3]))
(try (parald 1 2 3 4)
(except [NameError] True)
(else (assert False)))
(require [tests.resources.tlib [parald :as p]])
(assert (= (p 1 2 3) [9 1 2 3]))
(try (parald 1 2 3 4)
(except [NameError] True)
(else (assert False)))
(require [tests.resources.tlib [*]])
(assert (= (parald 1 2 3) [9 1 2 3])))
(defn test-require-native []
@ -1124,7 +1171,7 @@
(assert (= x [3 2 1]))
"success")
(except [NameError] "failure"))))
(require tests.native_tests.native_macros)
(require [tests.native_tests.native_macros [rev]])
(assert (= "success"
(try
(do (setv x [])

View File

@ -28,6 +28,18 @@
(assert (= 0 (+)))))
(defn test-add-unary []
"NATIVE: test that unary + calls __pos__"
(defclass X [object]
[__pos__ (fn [self] "called __pos__")])
(assert (= (+ (X)) "called __pos__"))
; Make sure the shadowed version works, too.
(setv f +)
(assert (= (f (X)) "called __pos__")))
(setv test_div (fn []
"NATIVE: Test division"
(assert (= 25 (/ 100 2 2)))

View File

@ -1,3 +1,5 @@
(import [hy.errors [HyTypeError]])
(defmacro rev [&rest body]
"Execute the `body` statements in reverse"
(quasiquote (do (unquote-splice (list (reversed body))))))
@ -37,6 +39,9 @@
(defmacro a-dict [] {1 2})
(assert (= (a-dict) {1 2}))
(defmacro a-set [] #{1 2})
(assert (= (a-set) #{1 2}))
(defmacro a-none [])
(assert (= (a-none) None))
@ -48,6 +53,26 @@
(defmacro bar [x y]
(foo x y))
(defn test-macro-kw []
"NATIVE: test that an error is raised when &kwonly, &kwargs, or &key is used in a macro"
(try
(eval '(defmacro f [&kwonly a b]))
(except [e HyTypeError]
(assert (= e.message "macros cannot use &kwonly")))
(else (assert false)))
(try
(eval '(defmacro f [&kwargs kw]))
(except [e HyTypeError]
(assert (= e.message "macros cannot use &kwargs")))
(else (assert false)))
(try
(eval '(defmacro f [&key {"kw" "xyz"}]))
(except [e HyTypeError]
(assert (= e.message "macros cannot use &key")))
(else (assert false))))
(defn test-fn-calling-macro []
"NATIVE: test macro calling a plain function"
(assert (= 3 (bar 1 2))))

View File

@ -8,13 +8,9 @@
(assert (= (x 1 2 3 4) 10))
(assert (= (x 1 2 3 4 5) 15))
; with strings
(assert (= (x "a")
"a"))
(assert (= (x "a" "b" "c")
"abc"))
; with lists
(assert (= (x ["a"])
["a"]))
(assert (= (x ["a"] ["b"] ["c"])
["a" "b" "c"]))))

View File

@ -1,7 +1,12 @@
from hy.macros import macro
from hy import HyList
from hy import HyList, HyInteger
@macro("qplah")
def tmac(*tree):
return HyList(tree)
return HyList((HyInteger(8), ) + tree)
@macro("parald")
def tmac2(*tree):
return HyList((HyInteger(9), ) + tree)

View File

@ -1,5 +1,5 @@
[tox]
envlist = py27,pypy,py33,py34,py35,flake8
envlist = py27,pypy,py33,py34,py35,py36,flake8
skipsdist = True
[testenv]