Merge branch 'master' into style-update

This commit is contained in:
gilch 2019-11-19 19:50:08 -07:00 committed by GitHub
commit 1663e5ccf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 6557 additions and 4652 deletions

View File

@ -1,16 +1,15 @@
sudo: false
dist: xenial
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "3.7-dev"
- pypy
- pypy3
- "3.7"
- "3.8"
- pypy3.5-6.0
install:
- pip install -r requirements-travis.txt
- pip install --process-dependency-links -e .
- pip install -e .
script: pytest
cache: pip
after_success: make coveralls

View File

@ -88,3 +88,11 @@
* Yoan Tournade <yoan@ytotech.com>
* Simon Gomizelj <simon@vodik.xyz>
* Yigong Wang <wang@yigo.ng>
* Oskar Kvist <oskar.kvist@gmail.com>
* Brandon T. Willard <brandonwillard@gmail.com>
* Andrew R. M. <nixy@nixy.moe>
* Tristan de Cacqueray <tdecacqu@redhat.com>
* Sören Tempel <soeren@soeren-tempel.net>
* Noah Snelson <noah.snelson@protonmail.com>
* Adam Porter <adam@alphapapa.net>
* Gábor Lipták <gliptak@gmail.com>

View File

@ -1,4 +1,4 @@
Copyright 2018 the authors.
Copyright 2019 the authors.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

193
NEWS.rst
View File

@ -5,15 +5,126 @@ Unreleased
Removals
------------------------------
* Dotted lists, `HyCons`, `cons`, `cons?`, and `list*` have been removed.
These were redundant with Python's built-in data structures and Hy's most
common model types (`HyExpression`, `HyList`, etc.).
* Python 2 is no longer supported.
* Support for attribute lists in `defclass` has been removed. Use `setv`
and `defn` instead.
* Literal keywords are no longer parsed differently in calls to functions
with certain names.
* `hy.contrib.multi` has been removed. Use ``cond`` or the PyPI package
``multipledispatch`` instead.
New Features
------------------------------
* Added special forms ``py`` to ``pys`` that allow Hy programs to include
inline Python code.
* All augmented assignment operators (except `%=` and `^=`) now allow
more than two arguments.
* PEP 3107 and PEP 526 function and variable annotations are now supported.
* Added function ``parse-args`` which parses arguments with ``argparse``.
Other Breaking Changes
------------------------------
* ``HySequence`` is now a subclass of ``tuple`` instead of ``list``.
Thus, a ``HyList`` will never be equal to a ``list``, and you can't
use ``.append``, ``.pop``, etc. on an expression or list.
Bug Fixes
------------------------------
* Statements in the second argument of `assert` are now executed.
* Fixed the expression of a while loop that contains statements being compiled twice.
* `hy2py` can now handle format strings.
* Fixed crashes from inaccessible history files.
* The unit tests no longer unintentionally import the internal Python module "test".
This allows them to pass when run inside the "slim" Python Docker images.
Misc. Improvements
------------------------------
* Drop the use of the long-abandoned `clint <https://github.com/kennethreitz/clint>`_ library
for colors. `colorama <https://github.com/tartley/colorama>`_ is now used instead.
0.17.0
==============================
**Warning**: Hy 0.17.x will be the last Hy versions to support Python 2,
and we expect 0.17.0 to be the only release in this line. By the time
0.18.0 is released (in 2020, after CPython 2 has ceased being developed),
Hy will only support Python 3.
Removals
------------------------------
* Python 3.4 is no longer supported.
New Features
------------------------------
* Python 3.8 is now supported.
* Format strings with embedded Hy code (e.g., `f"The sum is {(+ x y)}"`)
are now supported, even on Pythons earlier than 3.6.
* Added a special form `setx` to create Python 3.8 assignment expressions.
* Added new core functions `list?` and `tuple`.
* Gensyms now have a simpler format that's more concise when
mangled (e.g., `_hyx_XsemicolonXfooXvertical_lineX1235` is now
`_hyx_fooXUffffX1`).
Bug Fixes
------------------------------
* Fixed a crash caused by errors creating temporary files during
bytecode compilation.
0.16.0
==============================
Removals
------------------------------
* Empty expressions (`()`) are no longer legal at the top level.
New Features
------------------------------
* `eval` / `hy_eval` and `hy_compile` now accept an optional `compiler`
argument that enables the use of an existing `HyASTCompiler` instance.
* Keyword objects (not just literal keywords) can be called, as
shorthand for `(get obj :key)`, and they accept a default value
as a second argument.
* Minimal macro expansion namespacing has been implemented. As a result,
external macros no longer have to `require` their own macro
dependencies.
* Macros and tags now reside in module-level `__macros__` and `__tags__`
attributes.
Bug Fixes
------------------------------
* Cleaned up syntax and compiler errors.
* You can now call `defmain` with an empty lambda list.
* `require` now compiles to Python AST.
* Fixed circular `require`\s.
* Fixed module reloading.
* Fixed circular imports.
* Fixed errors from `from __future__ import ...` statements and missing
Hy module docstrings caused by automatic importing of Hy builtins.
* Fixed `__main__` file execution.
* Fixed bugs in the handling of unpacking forms in method calls and
attribute access.
* Fixed crashes on Windows when calling `hy-repr` on date and time
objects.
* Fixed a crash in `mangle` for some pathological inputs.
* Fixed incorrect mangling of some characters at low code points.
* Fixed a crash on certain versions of Python 2 due to changes in the
standard module `tokenize`.
0.15.0
==============================
Removals
------------------------------
* Dotted lists, `HyCons`, `cons`, `cons?`, and `list*` have been
removed. These were redundant with Python's built-in data structures
and Hy's most common model types (`HyExpression`, `HyList`, etc.).
* `&key` is no longer special in lambda lists. Use `&optional` instead.
* Tuple unpacking is no longer built into special forms for function
definition (`fn` etc.)
* Macros `ap-pipe` and `ap-compose` have been removed.
Anaphoric macros do not work well with point-free style programming,
in which case both threading macros and `comp` are more adequate.
* Lambda lists can no longer unpack tuples.
* `ap-pipe` and `ap-compose` have been removed. Use threading macros and
`comp` instead.
* `for/a` has been removed. Use `(for [:async ...] ...)` instead.
* `(except)` is no longer allowed. Use `(except [])` instead.
* `(import [foo])` is no longer allowed. Use `(import foo)` instead.
Other Breaking Changes
------------------------------
@ -21,48 +132,58 @@ Other Breaking Changes
This means you can no longer use alternative punctuation in place of
square brackets in special forms (e.g. `(fn (x) ...)` instead of
the standard `(fn [x] ...)`).
* Mangling rules have been overhauled, such that mangled names
are always legal Python identifiers
* `_` and `-` are now equivalent even as single-character names
* Mangling rules have been overhauled; now, mangled names are
always legal Python identifiers.
* `_` and `-` are now equivalent, even as single-character names.
* The REPL history variable `_` is now `*1`
* The REPL history variable `_` is now `*1`.
* Non-shadow unary `=`, `is`, `<`, etc. now evaluate their argument
instead of ignoring it. This change increases consistency a bit
and makes accidental unary uses easier to notice.
* `hy-repr` uses registered functions instead of methods
* `HyKeyword` no longer inherits from the string type and has been
made into its own object type.
instead of ignoring it.
* `list-comp`, `set-comp`, `dict-comp`, and `genexpr` have been replaced
by `lfor`, `sfor`, `dfor`, and `gfor`, respectively, which use a new
syntax and have additional features. All Python comprehensions can now
be written in Hy.
* `&`-parameters in lambda lists must now appear in the same order that
Python expects.
* Literal keywords now evaluate to themselves, and `HyKeyword` no longer
inherits from a Python string type
* `HySymbol` no longer inherits from `HyString`.
* `(except)` is no longer allowed. Use `(except [])` instead.
* `(import [foo])` is no longer allowed. Use `(import foo)` instead.
New Features
------------------------------
* Python 3.7 is now supported
* Added `mangle` and `unmangle` as core functions
* More REPL history result variables: `*2`, `*3`. Added last REPL error
variable: `*e`
* `defclass` in Python 3 now supports specifying metaclasses and other
keyword arguments
* Added a command-line option `-E` per CPython
* `while` and `for` are allowed to have empty bodies
* Python 3.7 is now supported.
* `while` and `for` are allowed to have empty bodies.
* `for` supports the various new clause types offered by `lfor`.
* `defclass` in Python 3 supports specifying metaclasses and other
keyword arguments.
* Added `mangle` and `unmangle` as core functions.
* Added more REPL history variables: `*2` and `*3`.
* Added a REPL variable holding the last exception: `*e`.
* Added a command-line option `-E` per CPython.
* Added a new module `hy.model_patterns`.
Bug Fixes
------------------------------
* `hy2py` should now output legal Python code equivalent to the input Hy
code in all cases, modulo some outstanding bugs in `astor`
* Fix `(return)` so it works correctly to exit a Python 2 generator
* Fixed a case where `->` and `->>` duplicated an argument
* Fixed bugs that caused `defclass` to drop statements or crash
* Fixed a REPL crash caused by illegle unicode escape string inputs
* `NaN` can no longer create an infinite loop during macro-expansion
* Fixed a bug that caused `try` to drop expressions
* Fixed a bug where the compiler didn't properly compile `unquote-splice`
code in all cases.
* Fixed `(return)` so it can exit a Python 2 generator.
* Fixed a case where `->` and `->>` duplicated an argument.
* Fixed bugs that caused `defclass` to drop statements or crash.
* Fixed a REPL crash caused by illegal backslash escapes.
* `NaN` can no longer create an infinite loop during macro-expansion.
* Fixed a bug that caused `try` to drop expressions.
* The compiler now properly recognizes `unquote-splice`.
* Trying to import a dotted name is now a syntax error, as in Python.
* `defmacro!` now allows optional arguments.
* Fixed handling of variables that are bound multiple times in a single
`let`.
Misc. Improvements
----------------------------
* `hy-repr` supports more standard types
* `hy-repr` uses registered functions instead of methods.
* `hy-repr` supports more standard types.
* `macroexpand-all` will now expand macros introduced by a `require` in the body of a macro.
0.14.0
==============================

View File

@ -1,38 +1,22 @@
Hy
==
[![Build Status](https://img.shields.io/travis/hylang/hy/master.svg)](https://travis-ci.org/hylang/hy)
[![Version](https://img.shields.io/pypi/v/hy.svg)](https://pypi.python.org/pypi/hy)
<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/).
Lisp and Python should love each other. Let's make it happen.
Hylarious Hacks
---------------
Hy is a Lisp dialect that's embedded in Python. Since Hy transforms its Lisp
code into Python abstract syntax tree (AST) objects, you have the whole
beautiful world of Python at your fingertips, in Lisp form.
* [Django + Lisp](https://github.com/paultag/djlisp/tree/master/djlisp)
* [Python `sh` Fun](https://twitter.com/paultag/status/314925996442796032)
* [Hy IRC Bot](https://github.com/hylang/hygdrop)
* [miniKanren in Hy](https://github.com/algernon/adderall)
To install the latest stable release of Hy, just use the command `pip3 install
--user hy`. Then you can start an interactive read-eval-print loop (REPL) with
the command `hy`, or run a Hy program with `hy myprogram.hy`.
OK, so, why?
------------
Well. Python is awesome. So awesome, that we have so many tools to alter the
language in a *core* way, but we never use them.
Why?
Well, I wrote Hy to help people realize one thing about Python:
It's really awesome.
Oh, and lisps are neat.
![Cuddles the Hacker](https://i.imgur.com/QbPMXTN.png)
(fan art from the one and only [doctormo](http://doctormo.deviantart.com/art/Cuddles-the-Hacker-372184766))
* [Why Hy?](http://docs.hylang.org/en/master/whyhy.html)
* [Tutorial](http://docs.hylang.org/en/master/tutorial.html)
Project
-------
@ -41,10 +25,14 @@ Project
* Documentation:
* stable, for use with the latest stable release: http://hylang.org/
* master, for use with the latest revision on GitHub: http://docs.hylang.org/en/master
* Quickstart: http://hylang.org/en/stable/quickstart.html
* Bug reports: We have no bugs! Your bugs are your own! (https://github.com/hylang/hy/issues)
* License: MIT (Expat)
* [Hacking on Hy](http://docs.hylang.org/en/master/hacking.html)
* [Contributor Guidelines](http://docs.hylang.org/en/master/hacking.html#contributor-guidelines)
* [Code of Conduct](http://docs.hylang.org/en/master/hacking.html#contributor-code-of-conduct)
* IRC: Join #hy on [freenode](https://webchat.freenode.net/)
* [Stack Overflow: The [hy] tag](https://stackoverflow.com/questions/tagged/hy)
![Cuddles the Hacker](https://i.imgur.com/QbPMXTN.png)
(fan art from the one and only [doctormo](http://doctormo.deviantart.com/art/Cuddles-the-Hacker-372184766))

View File

@ -1,15 +1,49 @@
import os
import importlib
import py
import pytest
import hy
import os
from hy._compat import PY3, PY35, PY36
from hy._compat import PY36, PY38
NATIVE_TESTS = os.path.join("", "tests", "native_tests", "")
_fspath_pyimport = py.path.local.pyimport
def pytest_ignore_collect(path, config):
return (("py36_only" in path.basename and not PY36) or
("py38_only" in path.basename and not PY38) or None)
def pyimport_patch_mismatch(self, **kwargs):
"""Lame fix for https://github.com/pytest-dev/py/issues/195"""
try:
return _fspath_pyimport(self, **kwargs)
except py.path.local.ImportMismatchError:
pkgpath = self.pypkgpath()
if pkgpath is None:
pkgroot = self.dirpath()
modname = self.purebasename
else:
pkgroot = pkgpath.dirpath()
names = self.new(ext="").relto(pkgroot).split(self.sep)
if names[-1] == "__init__":
names.pop()
modname = ".".join(names)
res = importlib.import_module(modname)
return res
py.path.local.pyimport = pyimport_patch_mismatch
def pytest_collect_file(parent, path):
if (path.ext == ".hy"
and NATIVE_TESTS in path.dirname + os.sep
and path.basename != "__init__.hy"
and not ("py3_only" in path.basename and not PY3)
and not ("py35_only" in path.basename and not PY35)
and not ("py36_only" in path.basename and not PY36)):
return pytest.Module(path, parent)
and NATIVE_TESTS in path.dirname + os.sep
and path.basename != "__init__.hy"):
pytest_mod = pytest.Module(path, parent)
return pytest_mod

View File

@ -2,7 +2,7 @@
#
# This file is execfile()d with the current directory set to its containing dir.
import re, os, sys, time, cgi
import re, os, sys, time, html
sys.path.append(os.path.abspath(".."))
extensions = ['sphinx.ext.intersphinx']
@ -28,7 +28,7 @@ copyright = u'%s the authors' % time.strftime('%Y')
version = ".".join(hy_version.split(".")[:-1])
# The full version, including alpha/beta/rc tags.
release = hy_version
hy_descriptive_version = cgi.escape(hy_version)
hy_descriptive_version = html.escape(hy_version)
if "+" in hy_version:
hy_descriptive_version += " <strong style='color: red;'>(unstable)</strong>"
@ -52,5 +52,4 @@ html_context = dict(
hy_descriptive_version = hy_descriptive_version)
intersphinx_mapping = dict(
py2 = ('https://docs.python.org/2/', None),
py = ('https://docs.python.org/3/', None))
py = ('https://docs.python.org/3/', None))

View File

@ -67,8 +67,8 @@ your choice to the keyword argument ``:placeholder`` of
.. code-block:: hy
(defclass Container [object]
[__init__ (fn [self value]
(setv self.value value))])
(defn __init__ (fn [self value]
(setv self.value value))))
(hy-repr-register Container :placeholder "HY THERE" (fn [x]
(+ "(Container " (hy-repr x.value) ")")))
(setv container (Container 5))

View File

@ -12,7 +12,6 @@ Contents:
:maxdepth: 3
loop
multi
profile
sequences
walk

View File

@ -1,110 +0,0 @@
========
defmulti
========
defn
----
.. versionadded:: 0.10.0
``defn`` lets you arity-overload a function by the given number of
args and/or kwargs. This version of ``defn`` works with regular syntax and
with the arity overloaded one. Inspired by Clojures take on ``defn``.
.. code-block:: clj
=> (require [hy.contrib.multi [defn]])
=> (defn fun
... ([a] "a")
... ([a b] "a b")
... ([a b c] "a b c"))
=> (fun 1)
"a"
=> (fun 1 2)
"a b"
=> (fun 1 2 3)
"a b c"
=> (defn add [a b]
... (+ a b))
=> (add 1 2)
3
defmulti
--------
.. versionadded:: 0.12.0
``defmulti``, ``defmethod`` and ``default-method`` lets you define
multimethods where a dispatching function is used to select between different
implementations of the function. Inspired by Clojure's multimethod and based
on the code by `Adam Bard`_.
.. code-block:: clj
=> (require [hy.contrib.multi [defmulti defmethod default-method]])
=> (defmulti area [shape]
... "calculate area of a shape"
... (:type shape))
=> (defmethod area "square" [square]
... (* (:width square)
... (:height square)))
=> (defmethod area "circle" [circle]
... (* (** (:radius circle) 2)
... 3.14))
=> (default-method area [shape]
... 0)
=> (area {:type "circle" :radius 0.5})
0.785
=> (area {:type "square" :width 2 :height 2})
4
=> (area {:type "non-euclid rhomboid"})
0
``defmulti`` is used to define the initial multimethod with name, signature
and code that selects between different implementations. In the example,
multimethod expects a single input that is type of dictionary and contains
at least key :type. The value that corresponds to this key is returned and
is used to selected between different implementations.
``defmethod`` defines a possible implementation for multimethod. It works
otherwise in the same way as ``defn``, but has an extra parameters
for specifying multimethod and which calls are routed to this specific
implementation. In the example, shapes with "square" as :type are routed to
first function and shapes with "circle" as :type are routed to second
function.
``default-method`` specifies default implementation for multimethod that is
called when no other implementation matches.
Interfaces of multimethod and different implementation don't have to be
exactly identical, as long as they're compatible enough. In practice this
means that multimethod should accept the broadest range of parameters and
different implementations can narrow them down.
.. code-block:: clj
=> (require [hy.contrib.multi [defmulti defmethod]])
=> (defmulti fun [&rest args]
... (len args))
=> (defmethod fun 1 [a]
... a)
=> (defmethod fun 2 [a b]
... (+ a b))
=> (fun 1)
1
=> (fun 1 2)
3
.. _Adam Bard: https://adambard.com/blog/implementing-multimethods-in-python/

View File

@ -17,7 +17,7 @@ profile/calls
--------------
``profile/calls`` allows you to create a call graph visualization.
**Note:** You must have `Graphviz <http://www.graphviz.org/Home.php>`_
**Note:** You must have `Graphviz <http://www.graphviz.org/>`_
installed for this to work.

View File

@ -206,6 +206,8 @@ Recursively performs all possible macroexpansions in form, using the ``require``
Macros
======
.. _let:
let
---
@ -231,14 +233,15 @@ But assignments via ``import`` are always hoisted to normal Python scope, and
likewise, ``defclass`` will assign the class to the Python scope,
even if it shares the name of a let binding.
Use ``__import__`` and ``type`` (or whatever metaclass) instead,
Use ``importlib.import_module`` and ``type`` (or whatever metaclass) instead,
if you must avoid this hoisting.
The ``let`` macro takes two parameters: a list defining *variables*
and the *body* which gets executed. *variables* is a vector of
variable and value pairs.
``let`` executes the variable assignments one-by-one, in the order written.
Like the ``let*`` of many other Lisps, ``let`` executes the variable
assignments one-by-one, in the order written::
.. code-block:: hy
@ -247,4 +250,8 @@ variable and value pairs.
... (print x y))
5 6
Unlike them, however, each ``(let …)`` form uses only one
namespace for all its assignments. Thus, ``(let [x 1 x (fn [] x)]
(x))`` returns a function object, not 1 as you might expect.
It is an error to use a let-bound name in a ``global`` or ``nonlocal`` form.

View File

@ -1,16 +1,11 @@
* `Julien Danjou <https://github.com/jd>`_
* `Morten Linderud <https://github.com/Foxboron>`_
* `J Kenneth King <https://github.com/agentultra>`_
* `Gergely Nagy <https://github.com/algernon>`_
* `Tuukka Turto <https://github.com/tuturto>`_
* `Karen Rustad <https://github.com/aldeka>`_
* `Abhishek L <https://github.com/theanalyst>`_
* `Christopher Allan Webber <https://github.com/cwebber>`_
* `Konrad Hinsen <https://github.com/khinsen>`_
* `Will Kahn-Greene <https://github.com/willkg>`_
* `Paul Tagliamonte <https://github.com/paultag>`_
* `Kodi B. Arfer <https://github.com/Kodiologist>`_
* `Nicolas Dandrimont <https://github.com/olasd>`_
* `Berker Peksag <https://github.com/berkerpeksag>`_
* `Clinton N. Dreisbach <https://github.com/cndreisbach>`_
* `han semaj <https://github.com/microamp>`_
* `Kodi Arfer <https://github.com/Kodiologist>`_
* `Julien Danjou <https://github.com/jd>`_
* `Rob Day <https://github.com/rkday>`_
* `Simon Gomizelj <https://github.com/vodik>`_
* `Ryan Gonzalez <https://github.com/refi64>`_
* `Abhishek Lekshmanan <https://github.com/theanalyst>`_
* `Morten Linderud <https://github.com/Foxboron>`_
* `Matthew Odendahl <https://github.com/gilch>`_
* `Paul Tagliamonte <https://github.com/paultag>`_
* `Brandon T. Willard <https://github.com/brandonwillard>`_

View File

@ -10,7 +10,7 @@ Usage: ``(names)``
This function can be used to get a list (actually, a ``frozenset``) of the
names of Hy's built-in functions, macros, and special forms. The output
also includes all Python reserved words. All names are in unmangled form
(e.g., ``list-comp`` rather than ``list_comp``).
(e.g., ``not-in`` rather than ``not_in``).
.. code-block:: hy

View File

@ -1,3 +1,5 @@
.. _hacking:
===============
Hacking on Hy
===============

View File

@ -1,36 +1,28 @@
Welcome to Hy's documentation!
==============================
The Hy Manual
=============
.. image:: _static/hy-logo-small.png
:alt: Hy
:align: left
:Try Hy: https://try-hy.appspot.com
:PyPI: https://pypi.python.org/pypi/hy
:Source: https://github.com/hylang/hy
:List: `hylang-discuss <https://groups.google.com/forum/#!forum/hylang-discuss>`_
:IRC: ``#hy`` on Freenode
:Build status:
.. image:: https://secure.travis-ci.org/hylang/hy.png
:alt: Travis CI
:target: http://travis-ci.org/hylang/hy
:IRC: irc://chat.freenode.net/hy
:Stack Overflow: `The [hy] tag <https://stackoverflow.com/questions/tagged/hy>`_
Hy is a wonderful dialect of Lisp that's embedded in Python.
Hy is a Lisp dialect that's embedded in Python. Since Hy transforms its Lisp
code into Python abstract syntax tree (AST) objects, you have the whole
beautiful world of Python at your fingertips, in Lisp form.
Since Hy transforms its Lisp code into the Python Abstract Syntax
Tree, you have the whole beautiful world of Python at your fingertips,
in Lisp form!
Documentation Index
===================
Contents:
To install the latest stable release of Hy, just use the command ``pip3 install
--user hy``. Then you can start an interactive read-eval-print loop (REPL) with
the command ``hy``, or run a Hy program with ``hy myprogram.hy``.
.. toctree::
:maxdepth: 3
quickstart
whyhy
tutorial
style-guide
language/index

View File

@ -1,3 +1,5 @@
.. _special-forms:
=================
Built-Ins
=================
@ -6,6 +8,48 @@ Hy features a number of special forms that are used to help generate
correct Python AST. The following are "special" forms, which may have
behavior that's slightly unexpected in some situations.
^
-
The ``^`` symbol is used to denote annotations in three different contexts:
- Standalone variable annotations.
- Variable annotations in a setv call.
- Function argument annotations.
They implement `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ and
`PEP 3107 <https://www.python.org/dev/peps/pep-3107/>`_.
Here is some example syntax of all three usages:
.. code-block:: clj
; Annotate the variable x as an int (equivalent to `x: int`).
(^int x)
; Can annotate with expressions if needed (equivalent to `y: f(x)`).
(^(f x) y)
; Annotations with an assignment: each annotation (int, str) covers the term that
; immediately follows.
; Equivalent to: x: int = 1; y = 2; z: str = 3
(setv ^int x 1 y 2 ^str z 3)
; Annotate a as an int, c as an int, and b as a str.
; Equivalent to: def func(a: int, b: str = None, c: int = 1): ...
(defn func [^int a &optional ^str b ^int [c 1]] ...)
The rules are:
- The value to annotate with is the value that immediately follows the caret.
- There must be no space between the caret and the value to annotate, otherwise it will be
interpreted as a bitwise XOR like the Python operator.
- The annotation always comes (and is evaluated) *before* the value being annotated. This is
unlike Python, where it comes and is evaluated *after* the value being annotated.
Note that variable annotations are only supported on Python 3.6+.
For annotating items with generic types, the of_ macro will likely be of use.
.
-
@ -224,6 +268,23 @@ Examples of usage:
.. note:: ``assoc`` modifies the datastructure in place and returns ``None``.
await
-----
``await`` creates an :ref:`await expression <py:await>`. It takes exactly one
argument: the object to wait for.
::
=> (import asyncio)
=> (defn/a main []
... (print "hello")
... (await (asyncio.sleep 1))
... (print "world"))
=> (asyncio.run (main))
hello
world
break
-----
@ -239,7 +300,7 @@ as the user enters *k*.
comment
----
-------
The ``comment`` macro ignores its body and always expands to ``None``.
Unlike linewise comments, the body of the ``comment`` macro must
@ -262,6 +323,8 @@ This is completely discarded and doesn't expand to anything, not even ``None``.
Hy
.. _cond:
cond
----
@ -321,48 +384,19 @@ is only called on 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:
do
----------
``do`` is used to evaluate each of its arguments and return the
last one. Return values from every other than the last argument are discarded.
It can be used in ``list-comp`` to perform more complex logic as shown in one
of the following examples.
``do`` (called ``progn`` in some Lisps) takes any number of forms,
evaluates them, and returns the value of the last one, or ``None`` if no
forms were provided.
Some example usage:
::
.. code-block:: clj
=> (if True
... (do (print "Side effects rock!")
... (print "Yeah, really!")))
Side effects rock!
Yeah, really!
;; assuming that (side-effect) is a function that we want to call for each
;; and every value in the list, but whose return value we do not care about
=> (list-comp (do (side-effect x)
... (if (< x 5) (* 2 x)
... (* 4 x)))
... (x (range 10)))
[0, 2, 4, 6, 8, 20, 24, 28, 32, 36]
``do`` can accept any number of arguments, from 1 to n.
=> (+ 1 (do (setv x (+ 1 1)) x))
3
doc / #doc
@ -400,6 +434,22 @@ Gets help for macros or tag macros, respectively.
Gets help for a tag macro function available in this module.
dfor
----
``dfor`` creates a :ref:`dictionary comprehension <py:dict>`. Its syntax
is the same as that of `lfor`_ except that the final value form must be
a literal list of two elements, the first of which becomes each key and
the second of which becomes each value.
.. code-block:: hy
=> (dfor x (range 5) [x (* x 10)])
{0: 0, 1: 10, 2: 20, 3: 30, 4: 40}
.. _setv:
setv
----
@ -416,30 +466,48 @@ For example:
=> (counter [1 2 3 4 5 2 3] 2)
2
They can be used to assign multiple variables at once:
You can provide more than one targetvalue pair, and the assignments will be made in order::
.. code-block:: hy
(setv x 1 y x x 2)
(print x y) ; => 2 1
=> (setv a 1 b 2)
(1L, 2L)
=> a
1L
=> b
2L
=>
You can perform parallel assignments or unpack the source value with square brackets and :ref:`unpack-iterable`::
(setv duo ["tim" "eric"])
(setv [guy1 guy2] duo)
(print guy1 guy2) ; => tim eric
(setv [letter1 letter2 #* others] "abcdefg")
(print letter1 letter2 others) ; => a b ['c', 'd', 'e', 'f', 'g']
setx
-----
Whereas ``setv`` creates an assignment statement, ``setx`` creates an assignment expression (see :pep:`572`). It requires Python 3.8 or later. Only one targetvalue pair is allowed, and the target must be a bare symbol, but the ``setx`` form returns the assigned value instead of ``None``.
::
=> (when (> (setx x (+ 1 2)) 0)
... (print x "is greater than 0"))
3 is greater than 0
.. _defclass:
defclass
--------
New classes are declared with ``defclass``. It can takes two optional parameters:
a vector defining a possible super classes and another vector containing
attributes of the new class as two item vectors.
New classes are declared with ``defclass``. It can take optional parameters in the following order:
a list defining (a) possible super class(es) and a string (:term:`py:docstring`).
.. code-block:: clj
(defclass class-name [super-class-1 super-class-2]
[attribute value]
"docstring"
(setv attribute1 value1)
(setv attribute2 value2)
(defn method [self] (print "hello!")))
@ -449,8 +517,8 @@ below:
.. code-block:: clj
=> (defclass Cat []
... [age None
... colour "white"]
... (setv age None)
... (setv colour "white")
...
... (defn speak [self] (print "Meow")))
@ -466,56 +534,53 @@ below:
defn
----
``defn`` macro is used to define functions. It takes three
parameters: the *name* of the function to define, a vector of *parameters*,
and the *body* of the function:
``defn`` is used to define functions. It requires two arguments: a name (given
as a symbol) and a list of parameters (also given as symbols). Any remaining
arguments constitute the body of the function.
.. code-block:: clj
(defn name [params] body)
(defn name [params] bodyform1 bodyform2...)
Parameters may have the following keywords in front of them:
If there at least two body forms, and the first of them is a string literal,
this string becomes the :term:`py:docstring` of the function.
Parameters may be prefixed with the following special symbols. If you use more
than one, they can only appear in the given order (so all `&optional`
parameters must precede any `&rest` parameter, `&rest` must precede `&kwonly`,
and `&kwonly` must precede `&kwargs`). This is the same order that Python
requires.
&optional
Parameter is optional. The parameter can be given as a two item list, where
the first element is parameter name and the second is the default value. The
parameter can be also given as a single item, in which case the default
value is ``None``.
All following parameters are optional. They may be given as two-argument lists,
where the first element is the parameter name and the second is the default value.
The parameter can also be given as a single item, in which case the default value
is ``None``.
The following example defines a function with one required positional argument
as well as three optional arguments. The first optional argument defaults to ``None``
and the latter two default to ``"("`` and ``")"``, respectively.
.. code-block:: clj
=> (defn total-value [value &optional [value-added-tax 10]]
... (+ (/ (* value value-added-tax) 100) value))
=> (defn format-pair [left-val &optional right-val [open-text "("] [close-text ")"]]
... (+ open-text (str left-val) ", " (str right-val) close-text))
=> (total-value 100)
110.0
=> (format-pair 3)
'(3, None)'
=> (total-value 100 1)
101.0
=> (format-pair "A" "B")
'(A, B)'
&kwargs
Parameter will contain 0 or more keyword arguments.
=> (format-pair "A" "B" "<" ">")
'<A, B>'
The following code examples defines a function that will print all keyword
arguments and their values.
.. code-block:: clj
=> (defn print-parameters [&kwargs kwargs]
... (for [(, k v) (.items kwargs)] (print k v)))
=> (print-parameters :parameter-1 1 :parameter-2 2)
parameter_1 1
parameter_2 2
; to avoid the mangling of '-' to '_', use unpacking:
=> (print-parameters #** {"parameter-1" 1 "parameter-2" 2})
parameter-1 1
parameter-2 2
=> (format-pair "A" :open-text "<" :close-text ">")
'<A, None>'
&rest
Parameter will contain 0 or more positional arguments. No other positional
arguments may be specified after this one.
The following parameter will contain a list of 0 or more positional arguments.
No other positional parameters may be specified after this one.
The following code example defines a function that can be given 0 to n
numerical parameters. It then sums every odd number and subtracts
@ -524,8 +589,8 @@ Parameters may have the following keywords in front of them:
.. code-block:: clj
=> (defn zig-zag-sum [&rest numbers]
(setv odd-numbers (list-comp x [x numbers] (odd? x))
even-numbers (list-comp x [x numbers] (even? x)))
(setv odd-numbers (lfor x numbers :if (odd? x) x)
even-numbers (lfor x numbers :if (even? x) x))
(- (sum odd-numbers) (sum even-numbers)))
=> (zig-zag-sum)
@ -538,11 +603,10 @@ Parameters may have the following keywords in front of them:
&kwonly
.. versionadded:: 0.12.0
Parameters that can only be called as keywords. Mandatory
keyword-only arguments are declared with the argument's name;
optional keyword-only arguments are declared as a two-element list
containing the argument name followed by the default value (as
with `&optional` above).
All following parmaeters can only be supplied as keywords.
Like ``&optional``, the parameter may be marked as optional by
declaring it as a two-element list containing the parameter name
following by the default value.
.. code-block:: clj
@ -568,8 +632,62 @@ Parameters may have the following keywords in front of them:
File "<input>", line 1, in <module>
TypeError: compare() missing 1 required keyword-only argument: 'keyfn'
Availability: Python 3.
&kwargs
Like ``&rest``, but for keyword arugments.
The following parameter will contain 0 or more keyword arguments.
The following code examples defines a function that will print all keyword
arguments and their values.
.. code-block:: clj
=> (defn print-parameters [&kwargs kwargs]
... (for [(, k v) (.items kwargs)] (print k v)))
=> (print-parameters :parameter-1 1 :parameter-2 2)
parameter_1 1
parameter_2 2
; to avoid the mangling of '-' to '_', use unpacking:
=> (print-parameters #** {"parameter-1" 1 "parameter-2" 2})
parameter-1 1
parameter-2 2
The following example uses all of ``&optional``, ``&rest``, ``&kwonly``, and
``&kwargs`` in order to show their interactions with each other. The function
renders an HTML tag.
It requires an argument ``tag-name``, a string which is the tag name.
It has one optional argument, ``delim``, which defaults to ``""`` and is placed
between each child.
The rest of the arguments, ``children``, are the tag's children or content.
A single keyword-only argument, ``empty``, is included and defaults to ``False``.
``empty`` changes how the tag is rendered if it has no children. Normally, a
tag with no children is rendered like ``<div></div>``. If ``empty`` is ``True``,
then it will render like ``<div />``.
The rest of the keyword arguments, ``props``, render as HTML attributes.
.. code-block:: clj
=> (defn render-html-tag [tag-name &optional [delim ""] &rest children &kwonly [empty False] &kwargs attrs]
... (setv rendered-attrs (.join " " (lfor (, key val) (.items attrs) (+ (unmangle (str key)) "=\"" (str val) "\""))))
... (if rendered-attrs ; If we have attributes, prefix them with a space after the tag name
... (setv rendered-attrs (+ " " rendered-attrs)))
... (setv rendered-children (.join delim children))
... (if (and (not children) empty)
... (+ "<" tag-name rendered-attrs " />")
... (+ "<" tag-name rendered-attrs ">" rendered-children "</" tag-name ">")))
=> (render-html-tag "div")
'<div></div'>
=> (render-html-tag "img" :empty True)
'<img />'
=> (render-html-tag "img" :id "china" :class "big-image" :empty True)
'<img id="china" class="big-image" />'
=> (render-html-tag "p" " --- " (render-html-tag "span" "" :class "fancy" "I'm fancy!") "I'm to the right of fancy" "I'm alone :(")
'<p><span class="fancy">I\'m fancy!</span> --- I\'m to right right of fancy --- I\'m alone :(</p>'
defn/a
------
@ -847,27 +965,45 @@ raising an exception.
=> (first [])
None
.. _for:
for
---
``for`` is used to call a function for each element in a list or vector.
The results of each call are discarded and the ``for`` expression returns
``None`` instead. The example code iterates over *collection* and for each
*element* in *collection* calls the ``side-effect`` function with *element*
as its argument:
``for`` is used to evaluate some forms for each element in an iterable
object, such as a list. The return values of the forms are discarded and
the ``for`` form returns ``None``.
.. code-block:: clj
::
;; assuming that (side-effect) is a function that takes a single parameter
(for [element collection] (side-effect element))
=> (for [x [1 2 3]]
... (print "iterating")
... (print x))
iterating
1
iterating
2
iterating
3
;; for can have an optional else block
(for [element collection] (side-effect element)
(else (side-effect-2)))
In its square-bracketed first argument, ``for`` allows the same types of
clauses as lfor_.
The optional ``else`` block is only executed if the ``for`` loop terminates
normally. If the execution is halted with ``break``, the ``else`` block does
not execute.
::
=> (for [x [1 2 3] :if (!= x 2) y [7 8]]
... (print x y))
1 7
1 8
3 7
3 8
Furthermore, the last argument of ``for`` can be an ``(else …)`` form.
This form is executed after the last iteration of the ``for``\'s
outermost iteration clause, but only if that outermost loop terminates
normally. If it's jumped out of with e.g. ``break``, the ``else`` is
ignored.
.. code-block:: clj
@ -888,43 +1024,6 @@ not execute.
loop finished
for/a
-----
``for/a`` behaves like ``for`` but is used to call a function for each
element generated by an asynchronous generator expression. The results
of each call are discarded and the ``for/a`` expression returns
``None`` instead.
.. code-block:: clj
;; assuming that (side-effect) is a function that takes a single parameter
(for/a [element (agen)] (side-effect element))
;; for/a can have an optional else block
(for/a [element (agen)] (side-effect element)
(else (side-effect-2)))
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 it
returns an iterable that evaluates values one by one instead of evaluating them
immediately.
.. code-block:: hy
=> (setv collection (range 10))
=> (setv filtered (genexpr x [x collection] (even? x)))
=> (list filtered)
[0, 2, 4, 6, 8]
.. _gensym:
gensym
@ -938,15 +1037,17 @@ written without accidental variable name clashes.
.. code-block:: clj
=> (gensym)
u':G_1235'
HySymbol('_G\uffff1')
=> (gensym "x")
u':x_1236'
HySymbol('_x\uffff2')
.. seealso::
Section :ref:`using-gensym`
.. _get:
get
---
@ -977,6 +1078,26 @@ successive elements in a nested structure. Example usage:
index that is out of bounds.
.. _gfor:
gfor
----
``gfor`` creates a :ref:`generator expression <py:genexpr>`. Its syntax
is the same as that of `lfor`_. The difference is that ``gfor`` returns
an iterator, which evaluates and yields values one at a time.
::
=> (setv accum [])
=> (list (take-while
... (fn [x] (< x 5))
... (gfor x (count) :do (.append accum x) x)))
[0, 1, 2, 3, 4]
=> accum
[0, 1, 2, 3, 4, 5]
global
------
@ -1000,6 +1121,8 @@ keyword, the second function would have raised a ``NameError``.
(set-a 5)
(print-a)
.. _if:
if / if* / if-not
-----------------
@ -1009,7 +1132,7 @@ if / if* / if-not
``if / if* / if-not`` respect Python *truthiness*, that is, a *test* fails if it
evaluates to a "zero" (including values of ``len`` zero, ``None``, and
``False``), and passes otherwise, but values with a ``__bool__`` method
(``__nonzero__`` in Python 2) can overrides this.
can override this.
The ``if`` macro is for conditionally selecting an expression for evaluation.
The result of the selected expression becomes the result of the entire ``if``
@ -1125,6 +1248,8 @@ that ``import`` can be used.
(import [sys [*]])
.. _fn:
fn
-----------
@ -1190,35 +1315,72 @@ last
6
list-comp
---------
.. _lfor:
``list-comp`` performs list comprehensions. 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. Some examples:
lfor
----
.. code-block:: clj
The comprehension forms ``lfor``, `sfor`_, `dfor`_, `gfor`_, and `for`_
are used to produce various kinds of loops, including Python-style
:ref:`comprehensions <py:comprehensions>`. ``lfor`` in particular
creates a list comprehension. A simple use of ``lfor`` is::
=> (setv collection (range 10))
=> (list-comp x [x collection])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
=> (list-comp (* x 2) [x collection])
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
=> (list-comp (* x 2) [x collection] (< x 5))
=> (lfor x (range 5) (* 2 x))
[0, 2, 4, 6, 8]
``x`` is the name of a new variable, which is bound to each element of
``(range 5)``. Each such element in turn is used to evaluate the value
form ``(* 2 x)``, and the results are accumulated into a list.
Here's a more complex example::
=> (lfor
... x (range 3)
... y (range 3)
... :if (!= x y)
... :setv total (+ x y)
... [x y total])
[[0, 1, 1], [0, 2, 2], [1, 0, 1], [1, 2, 3], [2, 0, 2], [2, 1, 3]]
When there are several iteration clauses (here, the pairs of forms ``x
(range 3)`` and ``y (range 3)``), the result works like a nested loop or
Cartesian product: all combinations are considered in lexicographic
order.
The general form of ``lfor`` is::
(lfor CLAUSES VALUE)
where the ``VALUE`` is an arbitrary form that is evaluated to produce
each element of the result list, and ``CLAUSES`` is any number of
clauses. There are several types of clauses:
- Iteration clauses, which look like ``LVALUE ITERABLE``. The ``LVALUE``
is usually just a symbol, but could be something more complicated,
like ``[x y]``.
- ``:async LVALUE ITERABLE``, which is an
:ref:`asynchronous <py:async for>` form of iteration clause.
- ``:do FORM``, which simply evaluates the ``FORM``. If you use
``(continue)`` or ``(break)`` here, they will apply to the innermost
iteration clause before the ``:do``.
- ``:setv LVALUE RVALUE``, which is equivalent to ``:do (setv LVALUE
RVALUE)``.
- ``:if CONDITION``, which is equivalent to ``:do (unless CONDITION
(continue))``.
For ``lfor``, ``sfor``, ``gfor``, and ``dfor``, variables are scoped as
if the comprehension form were its own function, so variables defined by
an iteration clause or ``:setv`` are not visible outside the form. In
fact, these forms are implemented as generator functions whenever they
contain Python statements, with the attendant consequences for calling
``return``. By contrast, ``for`` shares the caller's scope.
nonlocal
--------
.. versionadded:: 0.11.1
**PYTHON 3.0 AND UP ONLY!**
``nonlocal`` can be used to mark a symbol as not local to the current scope.
The parameters are the names of symbols to mark as nonlocal. This is necessary
to modify variables through nested ``fn`` scopes:
@ -1286,17 +1448,72 @@ parameter will be returned.
True
print
-----
.. _py-specialform:
``print`` is used to output on screen. Example usage:
of
--
``of`` is an alias for get, but with special semantics designed for handling PEP 484's generic
types.
``of`` has three forms:
- ``(of T)`` will simply become ``T``.
- ``(of T x)`` will become ``(get T x)``.
- ``(of T x y ...)`` (where the ``...`` represents zero or more arguments) will become
``(get T (, x y ...))``.
For instance:
.. code-block:: clj
(print "Hello world!")
(of str) ; => str
.. note:: ``print`` always returns ``None``.
(of List int) ; => List[int]
(of Set int) ; => Set[int]
(of Dict str str) ; => Dict[str, str]
(of Tuple str int) ; => Tuple[str, int]
(of Callable [int str] str) ; => Callable[[int, str], str]
py
--
``py`` parses the given Python code at compile-time and inserts the result into
the generated abstract syntax tree. Thus, you can mix Python code into a Hy
program. Only a Python expression is allowed, not statements; use
:ref:`pys-specialform` if you want to use Python statements. The value of the
expression is returned from the ``py`` form. ::
(print "A result from Python:" (py "'hello' + 'world'"))
The code must be given as a single string literal, but you can still use
macros, :ref:`eval`, and related tools to construct the ``py`` form. If you
want to evaluate some Python code that's only defined at run-time, try the
standard Python function :func:`eval`.
Python code need not syntactically round-trip if you use ``hy2py`` on a Hy
program that uses ``py`` or ``pys``. For example, comments will be removed.
.. _pys-specialform:
pys
---
As :ref:`py-specialform`, but the code can consist of zero or more statements,
including compound statements such as ``for`` and ``def``. ``pys`` always
returns ``None``. Also, the code string is dedented with
:func:`textwrap.dedent` before parsing, which allows you to intend the code to
match the surrounding Hy code, but significant leading whitespace in embedded
string literals will be removed. ::
(pys "myvar = 5")
(print "myvar is" myvar)
.. _quasiquote:
quasiquote
----------
@ -1316,6 +1533,8 @@ using ``unquote`` (``~``). The evaluated form can also be spliced using
; equivalent to '(foo bar baz)
.. _quote:
quote
-----
@ -1333,6 +1552,8 @@ alternatively be written using the apostrophe (``'``) symbol.
Hello World
.. _require:
require
-------
@ -1465,21 +1686,15 @@ the end of a function, put ``None`` there yourself.
=> (print (f 4))
None
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.
sfor
----
.. code-block:: hy
``sfor`` creates a set comprehension. ``(sfor CLAUSES VALUE)`` is
equivalent to ``(set (lfor CLAUSES VALUE))``. See `lfor`_.
=> (setv data [1 2 3 4 5 2 3 4 5 3 4 5])
=> (set-comp x [x data] (odd? x))
{1, 3, 5}
.. _cut:
cut
-----
@ -1587,9 +1802,14 @@ the given conditional is ``False``. The following shows the expansion of this ma
(do statement))
.. _unpack-iterable:
unpack-iterable, unpack-mapping
-------------------------------
(Also known as the splat operator, star operator, argument expansion, argument
explosion, argument gathering, and varargs, among others...)
``unpack-iterable`` and ``unpack-mapping`` allow an iterable or mapping
object (respectively) to provide positional or keywords arguments
(respectively) to a function.
@ -1608,20 +1828,14 @@ object (respectively) to provide positional or keywords arguments
=> (f #* [1 2] #** {"c" 3 "d" 4})
[1, 2, 3, 4]
With Python 3, you can unpack in an assignment list (:pep:`3132`).
Unpacking is allowed in a variety of contexts, and you can unpack
more than once in one expression (:pep:`3132`, :pep:`448`).
.. code-block:: clj
=> (setv [a #* b c] [1 2 3 4 5])
=> [a b c]
[1, [2, 3, 4], 5]
With Python 3.5 or greater, unpacking is allowed in more contexts than just
function calls, and you can unpack more than once in the same expression
(:pep:`448`).
.. code-block:: clj
=> [#* [1 2] #* [3 4]]
[1, 2, 3, 4]
=> {#** {1 2} #** {3 4}}
@ -1630,6 +1844,8 @@ function calls, and you can unpack more than once in the same expression
[1, 2, 3, 4]
.. _unquote:
unquote
-------
@ -1705,6 +1921,8 @@ following shows the expansion of the macro.
(if conditional (do statement))
.. _while:
while
-----
@ -1764,6 +1982,9 @@ prints
In condition
At end of outer loop
.. _with:
with
----
@ -1944,13 +2165,13 @@ infinite series without consuming infinite amount of memory.
=> (multiply (range 5) (range 5))
<generator object multiply at 0x978d8ec>
=> (list-comp value [value (multiply (range 10) (range 10))])
=> (list (multiply (range 10) (range 10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
=> (import random)
=> (defn random-numbers [low high]
... (while True (yield (.randint random low high))))
=> (list-comp x [x (take 15 (random-numbers 1 50))])
=> (list (take 15 (random-numbers 1 50)))
[7, 41, 6, 22, 32, 17, 5, 38, 18, 38, 17, 14, 23, 23, 19]
@ -1959,8 +2180,6 @@ yield-from
.. versionadded:: 0.9.13
**PYTHON 3.3 AND UP ONLY!**
``yield-from`` is used to call a subgenerator. This is useful if you
want your coroutine to be able to delegate its processes to another
coroutine, say, if using something fancy like

View File

@ -48,12 +48,6 @@ Command Line Options
`--spy` only works on REPL mode.
.. versionadded:: 0.9.11
.. cmdoption:: --show-tracebacks
Print extended tracebacks for Hy exceptions.
.. versionadded:: 0.9.12
.. cmdoption:: --repl-output-fn
Format REPL output using specific function (e.g., hy.contrib.hy-repr.hy-repr)

View File

@ -195,7 +195,9 @@ eval
``eval`` evaluates a quoted expression and returns the value. The optional
second and third arguments specify the dictionary of globals to use and the
module name. The globals dictionary defaults to ``(local)`` and the module name
defaults to the name of the current module.
defaults to the name of the current module. An optional fourth keyword parameter,
``compiler``, allows one to re-use an existing ``HyASTCompiler`` object for the
compilation step.
.. code-block:: clj
@ -238,19 +240,6 @@ otherwise ``False``. Return ``True`` if *coll* is empty.
True
.. _exec-fn:
exec
----
Equivalent to Python 3's built-in function :py:func:`exec`.
.. code-block:: clj
=> (exec "print(a + b)" {"a" 1} {"b" 2})
3
.. _float?-fn:
float?
@ -383,8 +372,7 @@ integer?
Usage: ``(integer? x)``
Returns `True` if *x* is an integer. For Python 2, this is
either ``int`` or ``long``. For Python 3, this is ``int``.
Returns `True` if *x* is an integer (``int``).
.. code-block:: hy
@ -798,6 +786,29 @@ Returns ``True`` if *x* is odd. Raises ``TypeError`` if
=> (odd? 0)
False
.. _parse-args:
parse-args
----------
Usage: ``(parse-args spec &optional args &kwargs parser-args)``
Return arguments namespace parsed from *args* or ``sys.argv`` with
:py:meth:`argparse.ArgumentParser.parse_args` according to *spec*.
*spec* should be a list of arguments which will be passed to repeated
calls to :py:meth:`argparse.ArgumentParser.add_argument`. *parser-args*
may be a list of keyword arguments to pass to the
:py:class:`argparse.ArgumentParser` constructor.
.. code-block:: hy
=> (parse-args [["strings" :nargs "+" :help "Strings"]
["-n" "--numbers" :action "append" :type 'int :help "Numbers"]]
["a" "b" "-n" "1" "-n" "2"]
:description "Parse strings and numbers from args")
Namespace(numbers=[1, 2], strings=['a', 'b'])
.. _partition-fn:
partition
@ -897,6 +908,24 @@ Returns the first logically-true value of ``(pred x)`` for any ``x`` in
True
.. _list?-fn:
list?
-----
Usage: ``(list? x)``
Returns ``True`` if *x* is a list.
.. code-block:: hy
=> (list? '(inc 41))
True
=> (list? '42)
False
.. _string?-fn:
string?
@ -904,7 +933,7 @@ string?
Usage: ``(string? x)``
Returns ``True`` if *x* is a string.
Returns ``True`` if *x* is a string (``str``).
.. code-block:: hy
@ -931,6 +960,25 @@ Returns ``True`` if *x* is a symbol.
=> (symbol? '[a b c])
False
.. _tuple?-fn:
tuple?
------
Usage: ``(tuple? x)``
Returns ``True`` if *x* is a tuple.
.. code-block:: hy
=> (tuple? (, 42 44))
True
=> (tuple? [42 44])
False
.. _zero?-fn:
zero?
@ -1403,4 +1451,3 @@ are available. Some of their names have been changed:
- ``dropwhile`` has been changed to ``drop-while``
- ``filterfalse`` has been changed to ``remove``

View File

@ -12,4 +12,5 @@ Contents:
syntax
api
core
model_patterns
internals

View File

@ -44,9 +44,9 @@ If this is causing issues,
it can be turned off globally by setting ``hy.models.PRETTY`` to ``False``,
or temporarily by using the ``hy.models.pretty`` context manager.
Hy also attempts to color pretty reprs using ``clint.textui.colored``.
This module has a flag to disable coloring,
and a method ``clean`` to strip colored strings of their color tags.
Hy also attempts to color pretty reprs and errors using ``colorama``. These can
be turned off globally by setting ``hy.models.COLORED`` and ``hy.errors.COLORED``,
respectively, to ``False``.
.. _hysequence:
@ -60,13 +60,17 @@ Adding a HySequence to another iterable object reuses the class of the
left-hand-side object, a useful behavior when you want to concatenate Hy
objects in a macro, for instance.
HySequences are (mostly) immutable: you can't add, modify, or remove
elements. You can still append to a variable containing a HySequence with
``+=`` and otherwise construct new HySequences out of old ones.
.. _hylist:
HyList
~~~~~~~~~~~~
~~~~~~
``hy.models.HyExpression`` is a :ref:`HySequence` for bracketed ``[]``
``hy.models.HyList`` is a :ref:`HySequence` for bracketed ``[]``
lists, which, when used as a top-level expression, translate to Python
list literals in the compilation phase.
@ -90,11 +94,6 @@ HyDict
``hy.models.HyDict`` inherits :ref:`HySequence` for curly-bracketed
``{}`` expressions, which compile down to a Python dictionary literal.
The decision of using a list instead of a dict as the base class for
``HyDict`` allows easier manipulation of dicts in macros, with the added
benefit of allowing compound expressions as dict keys (as, for instance,
the :ref:`HyExpression` Python class isn't hashable).
Atomic Models
-------------
@ -120,9 +119,7 @@ HyString
~~~~~~~~
``hy.models.HyString`` represents string literals (including bracket strings),
which compile down to unicode string literals in Python. ``HyStrings`` inherit
unicode objects in Python 2, and string objects in Python 3 (and are therefore
not encoding-dependent).
which compile down to unicode string literals (``str``) in Python.
``HyString``\s are immutable.
@ -140,15 +137,15 @@ HyBytes
~~~~~~~
``hy.models.HyBytes`` is like ``HyString``, but for sequences of bytes.
It inherits from ``bytes`` on Python 3 and ``str`` on Python 2.
It inherits from ``bytes``.
.. _hy_numeric_models:
Numeric Models
~~~~~~~~~~~~~~
``hy.models.HyInteger`` represents integer literals (using the
``long`` type on Python 2, and ``int`` on Python 3).
``hy.models.HyInteger`` represents integer literals, using the ``int``
type.
``hy.models.HyFloat`` represents floating-point literals.

View File

@ -1,3 +1,5 @@
.. _interop:
=====================
Hy <-> Python interop
=====================
@ -17,9 +19,11 @@ Hy and Python. For example, Python's ``str.format_map`` can be written
Using Python from Hy
====================
Using Python from Hy is nice and easy, you just have to :ref:`import` it.
You can embed Python code directly into a Hy program with the special operators
:ref:`py-specialform` and :ref:`pys-specialform`.
If you have the following in ``greetings.py`` in Python::
Using a Python module from Hy is nice and easy: you just have to :ref:`import`
it. If you have the following in ``greetings.py`` in Python::
def greet(name):
print("hello," name)
@ -47,8 +51,8 @@ If you save the following in ``greetings.hy``:
.. code-block:: clj
(setv *this-will-be-in-caps-and-underscores* "See?")
(defn greet [name] (print "hello from hy," name))
(setv this-will-have-underscores "See?")
(defn greet [name] (print "Hello from Hy," name))
Then you can use it directly from Python, by importing Hy before importing
the module. In Python::
@ -56,8 +60,8 @@ the module. In Python::
import hy
import greetings
greetings.greet("Foo") # prints "Hello from hy, Foo"
print(THIS_WILL_BE_IN_CAPS_AND_UNDERSCORES) # prints "See?"
greetings.greet("Foo") # prints "Hello from Hy, Foo"
print(greetings.this_will_have_underscores) # prints "See?"
If you create a package with Hy code, and you do the ``import hy`` in
``__init__.py``, you can then directly include the package. Of course, Hy still

View File

@ -0,0 +1,113 @@
==============
Model Patterns
==============
The module ``hy.model-patterns`` provides a library of parser combinators for
parsing complex trees of Hy models. Model patterns exist mostly to help
implement the compiler, but they can also be useful for writing macros.
A motivating example
--------------------
The kind of problem that model patterns are suited for is the following.
Suppose you want to validate and extract the components of a form like:
.. code-block:: clj
(setv form '(try
(foo1)
(foo2)
(except [EType1]
(foo3))
(except [e EType2]
(foo4)
(foo5))
(except []
(foo6))
(finally
(foo7)
(foo8))))
You could do this with loops and indexing, but it would take a lot of code and
be error-prone. Model patterns concisely express the general form of an
expression to be matched, like what a regular expression does for text. Here's
a pattern for a ``try`` form of the above kind:
.. code-block:: clj
(import [funcparserlib.parser [maybe many]])
(import [hy.model-patterns [*]])
(setv parser (whole [
(sym "try")
(many (notpexpr "except" "else" "finally"))
(many (pexpr
(sym "except")
(| (brackets) (brackets FORM) (brackets SYM FORM))
(many FORM)))
(maybe (dolike "else"))
(maybe (dolike "finally"))]))
You can run the parser with ``(.parse parser form)``. The result is:
.. code-block:: clj
(,
['(foo1) '(foo2)]
[
'([EType1] [(foo3)])
'([e EType2] [(foo4) (foo5)])
'([] [(foo6)])]
None
'((foo7) (foo8)))
which is conveniently utilized with an assignment such as ``(setv [body
except-clauses else-part finally-part] result)``. Notice that ``else-part``
will be set to ``None`` because there is no ``else`` clause in the original
form.
Usage
-----
Model patterns are implemented as funcparserlib_ parser combinators. We won't
reproduce funcparserlib's own documentation, but here are some important
built-in parsers:
- ``(+ ...)`` matches its arguments in sequence.
- ``(| ...)`` matches any one of its arguments.
- ``(>> parser function)`` matches ``parser``, then feeds the result through
``function`` to change the value that's produced on a successful parse.
- ``(skip parser)`` matches ``parser``, but doesn't add it to the produced
value.
- ``(maybe parser)`` matches ``parser`` if possible. Otherwise, it produces
the value ``None``.
- ``(some function)`` takes a predicate ``function`` and matches a form if it
satisfies the predicate.
The best reference for Hy's parsers is the docstrings (use ``(help
hy.model-patterns)``), but again, here are some of the more important ones:
- ``FORM`` matches anything.
- ``SYM`` matches any symbol.
- ``(sym "foo")`` or ``(sym ":foo")`` matches and discards (per ``skip``) the
named symbol or keyword.
- ``(brackets ...)`` matches the arguments in square brackets.
- ``(pexpr ...)`` matches the arguments in parentheses.
Here's how you could write a simple macro using model patterns:
.. code-block:: clj
(defmacro pairs [&rest args]
(import [funcparserlib.parser [many]])
(import [hy.model-patterns [whole SYM FORM]])
(setv [args] (->> args (.parse (whole [
(many (+ SYM FORM))]))))
`[~@(->> args (map (fn [x]
(, (name (get x 0)) (get x 1)))))])
(print (pairs a 1 b 2 c 3))
; => [["a" 1] ["b" 2] ["c" 3]]
A failed parse will raise ``funcparserlib.parser.NoParseError``.
.. _funcparserlib: https://github.com/vlasovskikh/funcparserlib

View File

@ -1,3 +1,5 @@
.. _syntax:
==============
Syntax
==============
@ -10,7 +12,7 @@ An identifier consists of a nonempty sequence of Unicode characters that are not
numeric literals
----------------
In addition to regular numbers, standard notation from Python 3 for non-base 10
In addition to regular numbers, standard notation from Python for non-base 10
integers is used. ``0x`` for Hex, ``0o`` for Octal, ``0b`` for Binary.
.. code-block:: clj
@ -42,7 +44,8 @@ string literal called a "bracket string" similar to Lua's long brackets.
Bracket strings have customizable delimiters, like the here-documents of other
languages. A bracket string begins with ``#[FOO[`` and ends with ``]FOO]``,
where ``FOO`` is any string not containing ``[`` or ``]``, including the empty
string. For example::
string. (If ``FOO`` is exactly ``f`` or begins with ``f-``, the bracket string
is interpreted as a :ref:`format string <syntax-fstrings>`.) For example::
=> (print #[["That's very kind of yuo [sic]" Tom wrote back.]])
"That's very kind of yuo [sic]" Tom wrote back.
@ -59,13 +62,48 @@ Plain string literals support :ref:`a variety of backslash escapes
literally, prefix the string with ``r``, as in ``r"slash\not"``. Bracket
strings are always raw strings and don't allow the ``r`` prefix.
Whether running under Python 2 or Python 3, Hy treats all string literals as
sequences of Unicode characters by default, and allows you to prefix a plain
string literal (but not a bracket string) with ``b`` to treat it as a sequence
of bytes. So when running under Python 3, Hy translates ``"foo"`` and
``b"foo"`` to the identical Python code, but when running under Python 2,
``"foo"`` is translated to ``u"foo"`` and ``b"foo"`` is translated to
``"foo"``.
Like Python, Hy treats all string literals as sequences of Unicode characters
by default. You may prefix a plain string literal (but not a bracket string)
with ``b`` to treat it as a sequence of bytes.
Unlike Python, Hy only recognizes string prefixes (``r``, etc.) in lowercase.
.. _syntax-fstrings:
format strings
--------------
A format string (or "f-string", or "formatted string literal") is a string
literal with embedded code, possibly accompanied by formatting commands. Hy
f-strings work much like :ref:`Python f-strings <py:f-strings>` except that the
embedded code is in Hy rather than Python, and they're supported on all
versions of Python.
::
=> (print f"The sum is {(+ 1 1)}.")
The sum is 2.
Since ``!`` and ``:`` are identifier characters in Hy, Hy decides where the
code in a replacement field ends, and any conversion or format specifier
begins, by parsing exactly one form. You can use ``do`` to combine several
forms into one, as usual. Whitespace may be necessary to terminate the form::
=> (setv foo "a")
=> (print f"{foo:x<5}")
NameError: name 'hyx_fooXcolonXxXlessHthan_signX5' is not defined
=> (print f"{foo :x<5}")
axxxx
Unlike Python, whitespace is allowed between a conversion and a format
specifier.
Also unlike Python, comments and backslashes are allowed in replacement fields.
Hy's lexer will still process the whole format string normally, like any other
string, before any replacement fields are considered, so you may need to
backslash your backslashes, and you can't comment out a closing brace or the
string delimiter.
.. _syntax-keywords:
@ -81,6 +119,11 @@ the error ``Keyword argument :foo needs a value``. To avoid this, you can quote
the keyword, as in ``(f ':foo)``, or use it as the value of another keyword
argument, as in ``(f :arg :foo)``.
Keywords can be called like functions as shorthand for ``get``. ``(:foo obj)``
is equivalent to ``(get obj :foo)``. An optional ``default`` argument is also
allowed: ``(:foo obj 2)`` or ``(:foo obj :default 2)`` returns ``2`` if ``(get
obj :foo)`` raises a ``KeyError``.
.. _mangling:
symbols

View File

@ -1,54 +0,0 @@
==========
Quickstart
==========
.. image:: _static/cuddles-transparent-small.png
:alt: Karen Rustard's Cuddles
(Thanks to Karen Rustad for Cuddles!)
**HOW TO GET HY REAL FAST**:
1. Create a `Virtual Python Environment
<https://pypi.python.org/pypi/virtualenv>`_.
2. Activate your Virtual Python Environment.
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::
=> (print "Hy!")
Hy!
=> (defn salutationsnm [name] (print (+ "Hy " name "!")))
=> (salutationsnm "YourName")
Hy YourName!
etc
6. Hit CTRL-D when you're done.
7. If you're familiar with Python, start the REPL using ``hy --spy`` to check what happens inside::
=> (+ "Hyllo " "World" "!")
'Hyllo ' + 'World' + '!'
'Hyllo World!'
*OMG! That's amazing! I want to write a Hy program.*
8. Open up an elite programming editor and type::
#! /usr/bin/env hy
(print "I was going to code in Python syntax, but then I got Hy.")
9. Save as ``awesome.hy``.
10. Make it executable::
chmod +x awesome.hy
11. And run your first Hy program::
./awesome.hy
12. Take a deep breath so as to not hyperventilate.
13. Smile villainously and sneak off to your hydeaway and do
unspeakable things.

View File

@ -39,7 +39,6 @@ into the making of Hy.
+ Look like a Lisp; do the right thing with it (e.g. dashes turn to underscores).
+ We're still Python. Most of the internals translate 1:1 to Python internals.
+ Use Unicode everywhere.
+ Fix the bad decisions in Python 2 when we can (see ``true_division``).
+ When in doubt, defer to Python.
+ If you're still unsure, defer to Clojure.
+ If you're even more unsure, defer to Common Lisp.

View File

@ -2,276 +2,174 @@
Tutorial
========
.. TODO
..
.. - How do I index into arrays or dictionaries?
.. - How do I do array ranges? e.g. x[5:] or y[2:10]
.. - Blow your mind with macros!
.. - Where's my banana???
.. image:: _static/cuddles-transparent-small.png
:alt: Karen Rustard's Cuddles
Welcome to the Hy tutorial!
This chapter provides a quick introduction to Hy. It assumes a basic background
in programming, but no specific prior knowledge of Python or Lisp.
In a nutshell, Hy is a Lisp dialect, but one that converts its
structure into Python ... literally a conversion into Python's abstract
syntax tree! (Or to put it in more crude terms, Hy is lisp-stick on a
Python!)
Lisp-stick on a Python
======================
This is pretty cool because it means Hy is several things:
Let's start with the classic::
- A Lisp that feels very Pythonic
- For Lispers, a great way to use Lisp's crazy powers but in the wide
world of Python's libraries (why yes, you now can write a Django
application in Lisp!)
- For Pythonistas, a great way to start exploring Lisp, from the
comfort of Python!
- For everyone: a pleasant language that has a lot of neat ideas!
(print "Hy, world!")
Now this tutorial assumes you're running Hy on Python 3. So know things
are a bit different if you're still using Python 2.
This program calls the :func:`print` function, which, like all of Python's
:ref:`built-in functions <py:built-in-funcs>`, is available in Hy.
All of Python's :ref:`binary and unary operators <py:expressions>` are
available, too, although ``==`` is spelled ``=`` in deference to Lisp
tradition. Here's how we'd use the addition operator ``+``::
Basic intro to Lisp for Pythonistas
===================================
(+ 1 3)
Okay, maybe you've never used Lisp before, but you've used Python!
This code returns ``4``. It's equivalent to ``1 + 3`` in Python and many other
languages. Languages in the `Lisp
<https://en.wikipedia.org/wiki/Lisp_(programming_language)>`_ family, including
Hy, use a prefix syntax: ``+``, just like ``print`` or ``sqrt``, appears before
all of its arguments. The call is delimited by parentheses, but the opening
parenthesis appears before the operator being called instead of after it, so
instead of ``sqrt(2)``, we write ``(sqrt 2)``. Multiple arguments, such as the
two integers in ``(+ 1 3)``, are separated by whitespace. Many operators,
including ``+``, allow more than two arguments: ``(+ 1 2 3)`` is equivalent to
``1 + 2 + 3``.
A "hello world" program in Hy is actually super simple. Let's try it:
Here's a more complex example::
.. code-block:: clj
(- (* (+ 1 3 88) 2) 8)
(print "hello world")
This code returns ``176``. Why? We can see the infix equivalent with the
command ``echo "(- (* (+ 1 3 88) 2) 8)" | hy2py``, which returns the Python
code corresponding to the given Hy code, or by passing the ``--spy`` option to
Hy when starting the REPL, which shows the Python equivalent of each input line
before the result. The infix equivalent in this case is:
See? Easy! As you may have guessed, this is the same as the Python
version of::
.. code-block:: python
print("hello world")
((1 + 3 + 88) * 2) - 8
To add up some super simple math, we could do:
To evaluate this infix expression, you'd of course evaluate the innermost
parenthesized expression first and work your way outwards. The same goes for
Lisp. Here's what we'd get by evaluating the above Hy code one step at a time::
.. code-block:: clj
(- (* (+ 1 3 88) 2) 8)
(- (* 92 2) 8)
(- 184 8)
176
(+ 1 3)
The basic unit of Lisp syntax, which is similar to a C or Python expression, is
the **form**. ``92``, ``*``, and ``(* 92 2)`` are all forms. A Lisp program
consists of a sequence of forms nested within forms. Forms are typically
separated from each other by whitespace, but some forms, such as string
literals (``"Hy, world!"``), can contain whitespace themselves. An
**expression** is a form enclosed in parentheses; its first child form, called
the **head**, determines what the expression does, and should generally be a
function, macro, or special operator. Functions are the most ordinary sort of
head, whereas macros (described in more detail below) are functions executed at
compile-time instead and return code to be executed at run-time. Special
operators are one of :ref:`a fixed set of names <special-forms>` that are
hard-coded into the compiler, and used to implement everything else.
Which would return 4 and would be the equivalent of:
Comments start with a ``;`` character and continue till the end of the line. A
comment is functionally equivalent to whitespace. ::
.. code-block:: clj
(print (** 2 64)) ; Max 64-bit unsigned integer value
1 + 3
Although ``#`` isn't a comment character in Hy, a Hy program can begin with a
`shebang line <https://en.wikipedia.org/wiki/Shebang_(Unix)>`_, which Hy itself
will ignore::
What you'll notice is that the first item in the list is the function
being called and the rest of the arguments are the arguments being
passed in. In fact, in Hy (as with most Lisps) we can pass in
multiple arguments to the plus operator:
#!/usr/bin/env hy
(print "Make me executable, and run me!")
.. code-block:: clj
Literals
========
(+ 1 3 55)
Hy has :ref:`literal syntax <syntax>` for all of the same data types that
Python does. Here's an example of Hy code for each type and the Python
equivalent.
Which would return 59.
============== ================ =================
Hy Python Type
============== ================ =================
``1`` ``1`` :class:`int`
``1.2`` ``1.2`` :class:`float`
``4j`` ``4j`` :class:`complex`
``True`` ``True`` :class:`bool`
``None`` ``None`` :class:`NoneType`
``"hy"`` ``'hy'`` :class:`str`
``b"hy"`` ``b'hy'`` :class:`bytes`
``(, 1 2 3)`` ``(1, 2, 3)`` :class:`tuple`
``[1 2 3]`` ``[1, 2, 3]`` :class:`list`
``#{1 2 3}`` ``{1, 2, 3}`` :class:`set`
``{1 2 3 4}`` ``{1: 2, 3: 4}`` :class:`dict`
============== ================ =================
Maybe you've heard of Lisp before but don't know much about it. Lisp
isn't as hard as you might think, and Hy inherits from Python, so Hy
is a great way to start learning Lisp. The main thing that's obvious
about Lisp is that there's a lot of parentheses. This might seem
confusing at first, but it isn't so hard. Let's look at some simple
math that's wrapped in a bunch of parentheses that we could enter into
the Hy interpreter:
In addition, Hy has a Clojure-style literal syntax for
:class:`fractions.Fraction`: ``1/3`` is equivalent to ``fractions.Fraction(1,
3)``.
.. code-block:: clj
(setv result (- (/ (+ 1 3 88) 2) 8))
This would return 38.0 But why? Well, we could look at the equivalent
expression in python::
result = ((1 + 3 + 88) / 2) - 8
If you were to try to figure out how the above were to work in python,
you'd of course figure out the results by solving each inner
parenthesis. That's the same basic idea in Hy. Let's try this
exercise first in Python::
result = ((1 + 3 + 88) / 2) - 8
# simplified to...
result = (92 / 2) - 8
# simplified to...
result = 46.0 - 8
# simplified to...
result = 38.0
Now let's try the same thing in Hy:
.. code-block:: clj
(setv result (- (/ (+ 1 3 88) 2) 8))
; simplified to...
(setv result (- (/ 92 2) 8))
; simplified to...
(setv result (- 46.0 8))
; simplified to...
(setv result 38.0)
As you probably guessed, this last expression with ``setv`` means to
assign the variable "result" to 38.0.
See? Not too hard!
This is the basic premise of Lisp. Lisp stands for "list
processing"; this means that the structure of the program is
actually lists of lists. (If you're familiar with Python lists,
imagine the entire same structure as above but with square brackets
instead, and you'll be able to see the structure above as both a
program and a data structure.) This is easier to understand with more
examples, so let's write a simple Python program, test it, and then
show the equivalent Hy program::
def simple_conversation():
print("Hello! I'd like to get to know you. Tell me about yourself!")
name = input("What is your name? ")
age = input("What is your age? ")
print("Hello " + name + "! I see you are " + age + " years old.")
simple_conversation()
If we ran this program, it might go like::
Hello! I'd like to get to know you. Tell me about yourself!
What is your name? Gary
What is your age? 38
Hello Gary! I see you are 38 years old.
Now let's look at the equivalent Hy program:
.. code-block:: clj
(defn simple-conversation []
(print "Hello! I'd like to get to know you. Tell me about yourself!")
(setv name (input "What is your name? "))
(setv age (input "What is your age? "))
(print (+ "Hello " name "! I see you are "
age " years old.")))
(simple-conversation)
If you look at the above program, as long as you remember that the
first element in each list of the program is the function (or
macro... we'll get to those later) being called and that the rest are
the arguments, it's pretty easy to figure out what this all means.
(As you probably also guessed, ``defn`` is the Hy method of defining
methods.)
Still, lots of people find this confusing at first because there's so
many parentheses, but there are plenty of things that can help make
this easier: keep indentation nice and use an editor with parenthesis
matching (this will help you figure out what each parenthesis pairs up
with) and things will start to feel comfortable.
There are some advantages to having a code structure that's actually a
very simple data structure as the core of Lisp is based on. For one
thing, it means that your programs are easy to parse and that the
entire actual structure of the program is very clearly exposed to you.
(There's an extra step in Hy where the structure you see is converted
to Python's own representations ... in "purer" Lisps such as Common
Lisp or Emacs Lisp, the data structure you see in the code and the
data structure that is executed is much more literally close.)
Another implication of this is macros: if a program's structure is a
simple data structure, that means you can write code that can write
code very easily, meaning that implementing entirely new language
features can be very fast. Previous to Hy, this wasn't very possible
for Python programmers ... now you too can make use of macros'
incredible power (just be careful to not aim them footward)!
Hy is a Lisp-flavored Python
============================
Hy converts to Python's own abstract syntax tree, so you'll soon start
to find that all the familiar power of python is at your fingertips.
You have full access to Python's data types and standard library in
Hy. Let's experiment with this in the hy interpreter::
The Hy REPL prints output in Python syntax by default::
=> [1 2 3]
[1, 2, 3]
=> {"dog" "bark"
... "cat" "meow"}
{'dog': 'bark', 'cat': 'meow'}
=> (, 1 2 3)
(1, 2, 3)
=> #{3 1 2}
{1, 2, 3}
=> 1/2
Fraction(1, 2)
Notice the last two lines: Hy has a fraction literal like Clojure.
If you start Hy like this (a shell alias might be helpful)::
But if you start Hy like this (a shell alias might be helpful)::
$ hy --repl-output-fn=hy.contrib.hy-repr.hy-repr
the interactive mode will use :ref:`hy-repr-fn` instead of Python's
native ``repr`` function to print out values, so you'll see values in
Hy syntax rather than Python syntax::
the interactive mode will use :ref:`hy-repr-fn` instead of Python's native
``repr`` function to print out values, so you'll see values in Hy syntax::
=> [1 2 3]
[1 2 3]
=> {"dog" "bark"
... "cat" "meow"}
{"dog" "bark" "cat" "meow"}
If you are familiar with other Lisps, you may be interested that Hy
supports the Common Lisp method of quoting:
.. code-block:: clj
Basic operations
================
=> '(1 2 3)
(1 2 3)
Set variables with :ref:`setv`::
You also have access to all the built-in types' nice methods::
(setv zone-plane 8)
=> (.strip " fooooo ")
"fooooo"
Access the elements of a list, dictionary, or other data structure with
:ref:`get`::
What's this? Yes indeed, this is precisely the same as::
(setv fruit ["apple" "banana" "cantaloupe"])
(print (get fruit 0)) ; => apple
(setv (get fruit 1) "durian")
(print (get fruit 1)) ; => durian
" fooooo ".strip()
Access a range of elements in an ordered structure with :ref:`cut`::
That's right---Lisp with dot notation! If we have this string
assigned as a variable, we can also do the following:
(print (cut "abcdef" 1 4)) ; => bcd
.. code-block:: clj
Conditional logic can be built with :ref:`if`::
(setv this-string " fooooo ")
(this-string.strip)
(if (= 1 1)
(print "Math works. The universe is safe.")
(print "Math has failed. The universe is doomed."))
What about conditionals?:
As in this example, ``if`` is called like ``(if CONDITION THEN ELSE)``. It
executes and returns the form ``THEN`` if ``CONDITION`` is true (according to
:class:`bool`) and ``ELSE`` otherwise. If ``ELSE`` is omitted, ``None`` is used
in its place.
.. code-block:: clj
What if you want to use more than form in place of the ``THEN`` or ``ELSE``
clauses, or in place of ``CONDITION``, for that matter? Use the special
operator :ref:`do` (known more traditionally in Lisp as ``progn``), which
combines several forms into one, returning the last::
(if (try-some-thing)
(print "this is if true")
(print "this is if false"))
(if (do (print "Let's check.") (= 1 1))
(do
(print "Math works.")
(print "The universe is safe."))
(do
(print "Math has failed.")
(print "The universe is doomed.")))
As you can tell above, the first argument to ``if`` is a truth test, the
second argument is the body if true, and the third argument (optional!)
is if false (ie. ``else``).
If you need to do more complex conditionals, you'll find that you
don't have ``elif`` available in Hy. Instead, you should use something
called ``cond``. In Python, you might do something like::
somevar = 33
if somevar > 50:
print("That variable is too big!")
elif somevar < 10:
print("That variable is too small!")
else:
print("That variable is jussssst right!")
In Hy, you would do:
.. code-block:: clj
For branching on more than one case, try :ref:`cond`::
(setv somevar 33)
(cond
@ -282,294 +180,140 @@ In Hy, you would do:
[True
(print "That variable is jussssst right!")])
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
for ``True`` -- that's because ``True`` will always be true, so if we get
this far, we'll always run that one!
The macro ``(when CONDITION THEN-1 THEN-2 …)`` is shorthand for ``(if CONDITION
(do THEN-1 THEN-2 …))``. ``unless`` works the same as ``when``, but inverts the
condition with ``not``.
You might notice above that if you have code like:
Hy's basic loops are :ref:`while` and :ref:`for`::
.. code-block:: clj
(setv x 3)
(while (> x 0)
(print x)
(setv x (- x 1))) ; => 3 2 1
(if some-condition
(body-if-true)
(body-if-false))
(for [x [1 2 3]]
(print x)) ; => 1 2 3
But wait! What if you want to execute more than one statement in the
body of one of these?
A more functional way to iterate is provided by the comprehension forms such as
:ref:`lfor`. Whereas ``for`` always returns ``None``, ``lfor`` returns a list
with one element per iteration. ::
You can do the following:
(print (lfor x [1 2 3] (* x 2))) ; => [2, 4, 6]
.. code-block:: clj
(if (try-some-thing)
(do
(print "this is if true")
(print "and why not, let's keep talking about how true it is!"))
(print "this one's still simply just false"))
Functions, classes, and modules
===============================
You can see that we used ``do`` to wrap multiple statements. If you're
familiar with other Lisps, this is the equivalent of ``progn``
elsewhere.
Define named functions with :ref:`defn`::
Comments start with semicolons:
(defn fib [n]
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))))
(print (fib 8)) ; => 21
.. code-block:: clj
Define anonymous functions with :ref:`fn`::
(print "this will run")
; (print "but this will not")
(+ 1 2 3) ; we'll execute the addition, but not this comment!
(print (list (filter (fn [x] (% x 2)) (range 10))))
; => [1, 3, 5, 7, 9]
Hashbang (``#!``) syntax is supported:
Special symbols in the parameter list of ``defn`` or ``fn`` allow you to
indicate optional arguments, provide default values, and collect unlisted
arguments::
.. code-block:: clj
(defn test [a b &optional c [d "x"] &rest e]
[a b c d e])
(print (test 1 2)) ; => [1, 2, None, 'x', ()]
(print (test 1 2 3 4 5 6 7)) ; => [1, 2, 3, 4, (5, 6, 7)]
#! /usr/bin/env hy
(print "Make me executable, and run me!")
Set a function parameter by name with a ``:keyword``::
Looping is not hard but has a kind of special structure. In Python,
we might do::
(test 1 2 :d "y") ; => [1, 2, None, 'y', ()]
for i in range(10):
print("'i' is now at " + str(i))
Define classes with :ref:`defclass`::
The equivalent in Hy would be:
(defclass FooBar []
(defn __init__ [self x]
(setv self.x x))
(defn get-x [self]
self.x))
.. code-block:: clj
Here we create a new instance ``fb`` of ``FooBar`` and access its attributes by
various means::
(for [i (range 10)]
(print (+ "'i' is now at " (str i))))
(setv fb (FooBar 15))
(print fb.x) ; => 15
(print (. fb x)) ; => 15
(print (.get-x fb)) ; => 15
(print (fb.get-x)) ; => 15
Note that syntax like ``fb.x`` and ``fb.get-x`` only works when the object
being invoked (``fb``, in this case) is a simple variable name. To get an
attribute or call a method of an arbitrary form ``FORM``, you must use the
syntax ``(. FORM x)`` or ``(.get-x FORM)``.
You can also import and make use of various Python libraries. For
example:
Access an external module, whether written in Python or Hy, with
:ref:`import`::
.. code-block:: clj
(import math)
(print (math.sqrt 2)) ; => 1.4142135623730951
(import os)
(if (os.path.isdir "/tmp/somedir")
(os.mkdir "/tmp/somedir/anotherdir")
(print "Hey, that path isn't there!"))
Python's context managers (``with`` statements) are used like this:
.. code-block:: clj
(with [f (open "/tmp/data.in")]
(print (.read f)))
which is equivalent to::
with open("/tmp/data.in") as f:
print(f.read())
And yes, we do have List comprehensions! In Python you might do::
odds_squared = [
pow(num, 2)
for num in range(100)
if num % 2 == 1]
In Hy, you could do these like:
.. code-block:: clj
(setv odds-squared
(list-comp
(pow num 2)
(num (range 100))
(= (% num 2) 1)))
.. code-block:: clj
; And, an example stolen shamelessly from a Clojure page:
; Let's list all the blocks of a Chessboard:
(list-comp
(, x y)
(x (range 8)
y "ABCDEFGH"))
; [(0, 'A'), (0, 'B'), (0, 'C'), (0, 'D'), (0, 'E'), (0, 'F'), (0, 'G'), (0, 'H'),
; (1, 'A'), (1, 'B'), (1, 'C'), (1, 'D'), (1, 'E'), (1, 'F'), (1, 'G'), (1, 'H'),
; (2, 'A'), (2, 'B'), (2, 'C'), (2, 'D'), (2, 'E'), (2, 'F'), (2, 'G'), (2, 'H'),
; (3, 'A'), (3, 'B'), (3, 'C'), (3, 'D'), (3, 'E'), (3, 'F'), (3, 'G'), (3, 'H'),
; (4, 'A'), (4, 'B'), (4, 'C'), (4, 'D'), (4, 'E'), (4, 'F'), (4, 'G'), (4, 'H'),
; (5, 'A'), (5, 'B'), (5, 'C'), (5, 'D'), (5, 'E'), (5, 'F'), (5, 'G'), (5, 'H'),
; (6, 'A'), (6, 'B'), (6, 'C'), (6, 'D'), (6, 'E'), (6, 'F'), (6, 'G'), (6, 'H'),
; (7, 'A'), (7, 'B'), (7, 'C'), (7, 'D'), (7, 'E'), (7, 'F'), (7, 'G'), (7, 'H')]
Python has support for various fancy argument and keyword arguments.
In Python we might see::
>>> def optional_arg(pos1, pos2, keyword1=None, keyword2=42):
... return [pos1, pos2, keyword1, keyword2]
...
>>> optional_arg(1, 2)
[1, 2, None, 42]
>>> optional_arg(1, 2, 3, 4)
[1, 2, 3, 4]
>>> optional_arg(keyword1=1, pos2=2, pos1=3, keyword2=4)
[3, 2, 1, 4]
The same thing in Hy::
=> (defn optional-arg [pos1 pos2 &optional keyword1 [keyword2 42]]
... [pos1 pos2 keyword1 keyword2])
=> (optional-arg 1 2)
[1 2 None 42]
=> (optional-arg 1 2 3 4)
[1 2 3 4]
You can call keyword arguments like this::
=> (optional-arg :keyword1 1
... :pos2 2
... :pos1 3
... :keyword2 4)
[3, 2, 1, 4]
You can unpack arguments with the syntax ``#* args`` and ``#** kwargs``,
similar to `*args` and `**kwargs` in Python::
=> (setv args [1 2])
=> (setv kwargs {"keyword2" 3
... "keyword1" 4})
=> (optional-arg #* args #** kwargs)
[1, 2, 4, 3]
Hy also supports ``*args`` and ``**kwargs`` in parameter lists. In Python::
def some_func(foo, bar, *args, **kwargs):
import pprint
pprint.pprint((foo, bar, args, kwargs))
The Hy equivalent:
.. code-block:: clj
(defn some-func [foo bar &rest args &kwargs kwargs]
(import pprint)
(pprint.pprint (, foo bar args kwargs)))
Finally, of course we need classes! In Python, we might have a class
like::
class FooBar(object):
"""
Yet Another Example Class
"""
def __init__(self, x):
self.x = x
def get_x(self):
"""
Return our copy of x
"""
return self.x
And we might use it like::
bar = FooBar(1)
print(bar.get_x())
In Hy:
.. code-block:: clj
(defclass FooBar [object]
"Yet Another Example Class"
(defn --init-- [self x]
(setv self.x x))
(defn get-x [self]
"Return our copy of x"
self.x))
And we can use it like:
.. code-block:: clj
(setv bar (FooBar 1))
(print (bar.get-x))
Or using the leading dot syntax!
.. code-block:: clj
(print (.get-x (FooBar 1)))
You can also do class-level attributes. In Python::
class Customer(models.Model):
name = models.CharField(max_length=255)
address = models.TextField()
notes = models.TextField()
In Hy:
.. code-block:: clj
(defclass Customer [models.Model]
[name (models.CharField :max-length 255})
address (models.TextField)
notes (models.TextField)])
Python can import a Hy module like any other module so long as Hy itself has
been imported first, which, of course, must have already happened if you're
running a Hy program.
Macros
======
One really powerful feature of Hy are macros. They are small functions that are
used to generate code (or data). When program written in Hy is started, the
macros are executed and their output is placed in the program source. After this,
the program starts executing normally. Very simple example:
Macros are the basic metaprogramming tool of Lisp. A macro is a function that
is called at compile time (i.e., when a Hy program is being translated to
Python :mod:`ast` objects) and returns code, which becomes part of the final
program. Here's a simple example::
.. code-block:: clj
(print "Executing")
(defmacro m []
(print "Now for a slow computation")
(setv x (% (** 10 10 7) 3))
(print "Done computing")
x)
(print "Value:" (m))
(print "Done executing")
=> (defmacro hello [person]
... `(print "Hello there," ~person))
=> (hello "Tuukka")
Hello there, Tuukka
If you run this program twice in a row, you'll see this::
The thing to notice here is that hello macro doesn't output anything on
screen. Instead it creates piece of code that is then executed and prints on
screen. This macro writes a piece of program that looks like this (provided that
we used "Tuukka" as parameter):
$ hy example.hy
Now for a slow computation
Done computing
Executing
Value: 1
Done executing
$ hy example.hy
Executing
Value: 1
Done executing
.. code-block:: clj
The slow computation is performed while compiling the program on its first
invocation. Only after the whole program is compiled does normal execution
begin from the top, printing "Executing". When the program is called a second
time, it is run from the previously compiled bytecode, which is equivalent to
simply::
(print "Hello there," "Tuukka")
(print "Executing")
(print "Value:" 1)
(print "Done executing")
We can also manipulate code with macros:
.. code-block:: clj
=> (defmacro rev [code]
... (setv op (last code) params (list (butlast code)))
... `(~op ~@params))
=> (rev (1 2 3 +))
6
The code that was generated with this macro just switched around some of the
elements, so by the time program started executing, it actually reads:
.. code-block:: clj
(+ 1 2 3)
Our macro ``m`` has an especially simple return value, an integer, which at
compile-time is converted to an integer literal. In general, macros can return
arbitrary Hy forms to be executed as code. There are several special operators
and macros that make it easy to construct forms programmatically, such as
:ref:`quote` (``'``), :ref:`quasiquote` (`````), :ref:`unquote` (``~``), and
:ref:`defmacro!`. The previous chapter has :ref:`a simple example <do-while>`
of using ````` and ``~`` to define a new control construct ``do-while``.
Sometimes it's nice to be able to call a one-parameter macro without
parentheses. Tag macros allow this. The name of a tag macro is typically
one character long, but since Hy operates well with Unicode, we aren't running
out of characters that soon:
.. code-block:: clj
parentheses. Tag macros allow this. The name of a tag macro is often just one
character long, but since Hy allows most Unicode characters in the name of a
macro (or ordinary variable), you won't out of characters soon. ::
=> (deftag ↻ [code]
... (setv op (last code) params (list (butlast code)))
@ -577,106 +321,23 @@ out of characters that soon:
=> #↻(1 2 3 +)
6
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 ``->``.
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
What if you want to use a macro that's defined in a different module?
``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 translation from Hy to Python. Instead, use :ref:`require`,
which imports the module and makes macros available at compile-time.
``require`` uses the same syntax as ``import``. ::
=> (require tutorial.macros)
=> (tutorial.macros.rev (1 2 3 +))
6
Hy <-> Python interop
=====================
Next steps
==========
Using Hy from Python
--------------------
You now know enough to be dangerous with Hy. You may now smile villainously and
sneak off to your Hydeaway to do unspeakable things.
You can use Hy modules in Python!
If you save the following in ``greetings.hy``:
.. code-block:: clj
(defn greet [name] (print "hello from hy," name))
Then you can use it directly from Python, by importing Hy before importing
the module. In Python::
import hy
import greetings
greetings.greet("Foo")
Using Python from Hy
--------------------
You can also use any Python module in Hy!
If you save the following in ``greetings.py`` in Python::
def greet(name):
print("hello, %s" % (name))
You can use it in Hy (see :ref:`import`):
.. code-block:: clj
(import greetings)
(.greet greetings "foo")
More information on :doc:`../language/interop`.
Protips!
========
Hy also features something known as the "threading macro", a really neat
feature of Clojure's. The "threading macro" (written as ``->``) is used
to avoid deep nesting of expressions.
The threading macro inserts each expression into the next expression's first
argument place.
Let's take the classic:
.. code-block:: clj
(require [hy.contrib.loop [loop]])
(loop (print (eval (read))))
Rather than write it like that, we can write it as follows:
.. code-block:: clj
(require [hy.contrib.loop [loop]])
(-> (read) (eval) (print) (loop))
Now, using `python-sh <http://amoffat.github.com/sh/>`_, we can show
how the threading macro (because of python-sh's setup) can be used like
a pipe:
.. code-block:: clj
=> (import [sh [cat grep wc]])
=> (-> (cat "/usr/share/dict/words") (grep "-E" "^hy") (wc "-l"))
210
Which, of course, expands out to:
.. code-block:: clj
(wc (grep (cat "/usr/share/dict/words") "-E" "^hy") "-l")
Much more readable, no? Use the threading macro!
Refer to Python's documention for the details of Python semantics, and the rest
of this manual for Hy-specific features. Like Hy itself, the manual is
incomplete, but :ref:`contributions <hacking>` are always welcome.

142
docs/whyhy.rst Normal file
View File

@ -0,0 +1,142 @@
=======
Why Hy?
=======
Hy is a multi-paradigm general-purpose programming language in the `Lisp family
<https://en.wikipedia.org/wiki/Lisp_(programming_language)>`_. It's implemented
as a kind of alternative syntax for Python. Compared to Python, Hy offers a
variety of extra features, generalizations, and syntactic simplifications, as
would be expected of a Lisp. Compared to other Lisps, Hy provides direct access
to Python's built-ins and third-party Python libraries, while allowing you to
freely mix imperative, functional, and object-oriented styles of programming.
Hy versus Python
----------------
The first thing a Python programmer will notice about Hy is that it has Lisp's
traditional parenthesis-heavy prefix syntax in place of Python's C-like infix
syntax. For example, ``print("The answer is", 2 + object.method(arg))`` could
be written ``(print "The answer is" (+ 2 (.method object arg)))`` in Hy.
Consequently, Hy is free-form: structure is indicated by parentheses rather
than whitespace, making it convenient for command-line use.
As in other Lisps, the value of a simplistic syntax is that it facilitates
Lisp's signature feature: `metaprogramming
<https://en.wikipedia.org/wiki/Metaprogramming>`_ through macros, which are
functions that manipulate code objects at compile time to produce new code
objects, which are then executed as if they had been part of the original code.
In fact, Hy allows arbitrary computation at compile-time. For example, here's a
simple macro that implements a C-style do-while loop, which executes its body
for as long as the condition is true, but at least once.
.. _do-while:
::
(defmacro do-while [condition &rest body]
`(do
~body
(while ~condition
~body)))
(setv x 0)
(do-while x
(print "This line is executed once."))
Hy also removes Python's restrictions on mixing expressions and statements,
allowing for more direct and functional code. For example, Python doesn't allow
:ref:`with <py:with>` blocks, which close a resource once you're done using it,
to return values. They can only execute a set of statements:
.. code-block:: python
with open("foo") as o:
f1 = o.read()
with open("bar") as o:
f2 = o.read()
print(len(f1) + len(f2))
In Hy, :ref:`with` returns the value of its last body form, so you can use it
like an ordinary function call::
(print (+
(len (with [o (open "foo")] (.read o))
(len (with [o (open "bar")] (.read o))))))
To be even more concise, you can put a ``with`` form in a :ref:`generator
expression <gfor>`::
(print (sum (gfor
filename ["foo" "bar"]
(len (with [o (open filename)] (.read o))))))
Finally, Hy offers several generalizations to Python's binary operators.
Operators can be given more than two arguments (e.g., ``(+ 1 2 3)``), including
augmented assignment operators (e.g., ``(+= x 1 2 3)``). They are also provided
as ordinary first-class functions of the same name, allowing them to be passed
to higher-order functions: ``(sum xs)`` could be written ``(reduce + xs)``.
The Hy compiler works by reading Hy source code into Hy model objects and
compiling the Hy model objects into Python abstract syntax tree (:py:mod:`ast`)
objects. Python AST objects can then be compiled and run by Python itself,
byte-compiled for faster execution later, or rendered into Python source code.
You can even :ref:`mix Python and Hy code in the same project, or even the same
file,<interop>` which can be a good way to get your feet wet in Hy.
Hy versus other Lisps
---------------------
At run-time, Hy is essentially Python code. Thus, while Hy's design owes a lot
to `Clojure <https://clojure.org>`_, it is more tightly coupled to Python than
Clojure is to Java; a better analogy is `CoffeeScript's
<https://coffeescript.org>`_ relationship to JavaScript. Python's built-in
:ref:`functions <py:built-in-funcs>` and :ref:`data structures
<py:bltin-types>` are directly available::
(print (int "deadbeef" :base 16)) ; 3735928559
(print (len [1 10 100])) ; 3
The same goes for third-party Python libraries from `PyPI <https://pypi.org>`_
and elsewhere. Here's a tiny `CherryPy <https://cherrypy.org>`_ web application
in Hy::
(import cherrypy)
(defclass HelloWorld []
(#@ cherrypy.expose (defn index [self]
"Hello World!")))
(cherrypy.quickstart (HelloWorld))
You can even run Hy on `PyPy <https://pypy.org>`_ for a particularly speedy
Lisp.
Like all Lisps, Hy is `homoiconic
<https://en.wikipedia.org/wiki/Homoiconicity>`_. Its syntax is represented not
with cons cells or with Python's basic data structures, but with simple
subclasses of Python's basic data structures called :ref:`models <models>`.
Using models in place of plain ``list``\s, ``set``\s, and so on has two
purposes: models can keep track of their line and column numbers for the
benefit of error messages, and models can represent syntactic features that the
corresponding primitive type can't, such as the order in which elements appear
in a set literal. However, models can be concatenated and indexed just like
plain lists, and you can return ordinary Python types from a macro or give them
to ``eval`` and Hy will automatically promote them to models.
Hy takes much of its semantics from Python. For example, Hy is a Lisp-1 because
Python functions use the same namespace as objects that aren't functions. In
general, any Python code should be possible to literally translate to Hy. At
the same time, Hy goes to some lengths to allow you to do typical Lisp things
that aren't straightforward in Python. For example, Hy provides the
aforementioned mixing of statements and expressions, :ref:`name mangling
<mangling>` that transparently converts symbols with names like ``valid?`` to
Python-legal identifiers, and a :ref:`let` macro to provide block-level scoping
in place of Python's usual function-level scoping.
Overall, Hy, like Common Lisp, is intended to be an unopinionated big-tent
language that lets you do what you want. If you're interested in a more
small-and-beautiful approach to Lisp, in the style of Scheme, check out
`Hissp <https://github.com/gilch/hissp>`_, another Lisp embedded in Python
that was created by a Hy developer.

112
fastentrypoints.py Normal file
View File

@ -0,0 +1,112 @@
# noqa: D300,D400
# Copyright (c) 2016, Aaron Christianson
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
Monkey patch setuptools to write faster console_scripts with this format:
import sys
from mymodule import entry_function
sys.exit(entry_function())
This is better.
(c) 2016, Aaron Christianson
http://github.com/ninjaaron/fast-entry_points
'''
from setuptools.command import easy_install
import re
TEMPLATE = '''\
# -*- coding: utf-8 -*-
# EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}'
__requires__ = '{3}'
import re
import sys
from {0} import {1}
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit({2}())'''
@classmethod
def get_args(cls, dist, header=None): # noqa: D205,D400
"""
Yield write_script() argument tuples for a distribution's
console_scripts and gui_scripts entry points.
"""
if header is None:
# pylint: disable=E1101
header = cls.get_header()
spec = str(dist.as_requirement())
for type_ in 'console', 'gui':
group = type_ + '_scripts'
for name, ep in dist.get_entry_map(group).items():
# ensure_safe_name
if re.search(r'[\\/]', name):
raise ValueError("Path separators not allowed in script names")
script_text = TEMPLATE.format(
ep.module_name, ep.attrs[0], '.'.join(ep.attrs),
spec, group, name)
# pylint: disable=E1101
args = cls._get_script_args(type_, name, header, script_text)
for res in args:
yield res
# pylint: disable=E1101
easy_install.ScriptWriter.get_args = get_args
def main():
import os
import re
import shutil
import sys
dests = sys.argv[1:] or ['.']
filename = re.sub('\.pyc$', '.py', __file__)
for dst in dests:
shutil.copy(filename, dst)
manifest_path = os.path.join(dst, 'MANIFEST.in')
setup_path = os.path.join(dst, 'setup.py')
# Insert the include statement to MANIFEST.in if not present
with open(manifest_path, 'a+') as manifest:
manifest.seek(0)
manifest_content = manifest.read()
if 'include fastentrypoints.py' not in manifest_content:
manifest.write(('\n' if manifest_content else '') +
'include fastentrypoints.py')
# Insert the import statement to setup.py if not present
with open(setup_path, 'a+') as setup:
setup.seek(0)
setup_content = setup.read()
if 'import fastentrypoints' not in setup_content:
setup.seek(0)
setup.truncate()
setup.write('import fastentrypoints\n' + setup_content)

View File

@ -5,16 +5,19 @@ import os, subprocess, runpy
os.chdir(os.path.split(os.path.abspath(__file__))[0])
VERSIONFILE = os.path.join("hy", "version.py")
try:
__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__))
if "HY_VERSION" in os.environ:
__version__ = os.environ["HY_VERSION"]
else:
try:
__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, OSError):
if os.path.exists(VERSIONFILE):
__version__ = runpy.run_path(VERSIONFILE)['__version__']
else:
__version__ = "unknown"
except (subprocess.CalledProcessError, OSError):
if os.path.exists(VERSIONFILE):
__version__ = runpy.run_path(VERSIONFILE)['__version__']
else:
__version__ = "unknown"

View File

@ -5,6 +5,16 @@ except ImportError:
__version__ = 'unknown'
def _initialize_env_var(env_var, default_val):
import os, distutils.util
try:
res = bool(distutils.util.strtobool(
os.environ.get(env_var, str(default_val))))
except ValueError as e:
res = default_val
return res
from hy.models import HyExpression, HyInteger, HyKeyword, HyComplex, HyString, HyBytes, HySymbol, HyFloat, HyDict, HyList, HySet # NOQA
@ -12,5 +22,5 @@ import hy.importer # NOQA
# we import for side-effects.
from hy.core.language import read, read_str, mangle, unmangle # NOQA
from hy.importer import hy_eval as eval # NOQA
from hy.lex import read, read_str, mangle, unmangle # NOQA
from hy.compiler import hy_eval as eval # NOQA

View File

@ -11,5 +11,5 @@ import sys
if len(sys.argv) > 1:
sys.argv.pop(0)
imp.load_source("__main__", sys.argv[0])
hy.importer._import_from_path('__main__', sys.argv[0])
sys.exit(0) # right?

View File

@ -1,62 +1,16 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
try:
import __builtin__ as builtins
except ImportError:
import builtins # NOQA
try:
from py_compile import MAGIC, wr_long
except ImportError:
# py_compile.MAGIC removed and imp.get_magic() deprecated in Python 3.4
from importlib.util import MAGIC_NUMBER as MAGIC # NOQA
import sys
def wr_long(f, x):
"""Internal; write a 32-bit int to a file in little-endian order."""
f.write(bytes([x & 0xff,
(x >> 8) & 0xff,
(x >> 16) & 0xff,
(x >> 24) & 0xff]))
import sys, keyword
PY3 = sys.version_info[0] >= 3
PY35 = sys.version_info >= (3, 5)
PY36 = sys.version_info >= (3, 6)
PY37 = sys.version_info >= (3, 7)
PY38 = sys.version_info >= (3, 8)
# The value of UCS4 indicates whether Unicode strings are stored as UCS-4.
# It is always true on Pythons >= 3.3, which use USC-4 on all systems.
UCS4 = sys.maxunicode == 0x10FFFF
str_type = str if PY3 else unicode # NOQA
bytes_type = bytes if PY3 else str # NOQA
long_type = int if PY3 else long # NOQA
string_types = str if PY3 else basestring # NOQA
if PY3:
exec('def raise_empty(t, *args): raise t(*args) from None')
else:
def raise_empty(t, *args):
raise t(*args)
def isidentifier(x):
if x in ('True', 'False', 'None', 'print'):
# `print` is special-cased here because Python 2's
# keyword.iskeyword will count it as a keyword, but we
# use the __future__ feature print_function, which makes
# it a non-keyword.
return True
if keyword.iskeyword(x):
return False
if PY3:
return x.isidentifier()
if x.rstrip() != x:
return False
import tokenize as T
from io import StringIO
def reraise(exc_type, value, traceback=None):
try:
tokens = list(T.generate_tokens(StringIO(x).readline))
except T.TokenError:
return False
return len(tokens) == 2 and tokens[0][0] == T.NAME
raise value.with_traceback(traceback)
finally:
traceback = None

View File

@ -1,35 +1,49 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
from __future__ import print_function
import colorama
colorama.init()
import argparse
import code
import ast
import sys
import os
import io
import importlib
import py_compile
import traceback
import runpy
import types
import time
import linecache
import hashlib
import codeop
import builtins
import astor.code_gen
import hy
from hy.lex import LexException, PrematureEndOfInput
from hy.lex.parser import mangle
from hy.compiler import HyTypeError
from hy.importer import (hy_eval, import_buffer_to_module,
import_file_to_ast, import_file_to_hst,
import_buffer_to_ast, import_buffer_to_hst)
from hy.completer import completion
from hy.completer import Completer
from hy.errors import HyIOError
from hy.lex import hy_parse, mangle
from contextlib import contextmanager
from hy.lex.exceptions import PrematureEndOfInput
from hy.compiler import (HyASTCompiler, hy_eval, hy_compile,
hy_ast_compile_flags)
from hy.errors import (HyLanguageError, HyRequireError, HyMacroExpansionError,
filtered_hy_exceptions, hy_exc_handler)
from hy.importer import runhy
from hy.completer import completion, Completer
from hy.macros import macro, require
from hy.models import HyExpression, HyString, HySymbol
from hy._compat import builtins, PY3
sys.last_type = None
sys.last_value = None
sys.last_traceback = None
class HyQuitter(object):
@ -48,15 +62,202 @@ class HyQuitter(object):
pass
raise SystemExit(code)
class HyHelper(object):
def __repr__(self):
return ("Use (help) for interactive help, or (help object) for help "
"about object.")
def __call__(self, *args, **kwds):
import pydoc
return pydoc.help(*args, **kwds)
builtins.quit = HyQuitter('quit')
builtins.exit = HyQuitter('exit')
builtins.help = HyHelper()
@contextmanager
def extend_linecache(add_cmdline_cache):
_linecache_checkcache = linecache.checkcache
def _cmdline_checkcache(*args):
_linecache_checkcache(*args)
linecache.cache.update(add_cmdline_cache)
linecache.checkcache = _cmdline_checkcache
yield
linecache.checkcache = _linecache_checkcache
class HyREPL(code.InteractiveConsole):
_codeop_maybe_compile = codeop._maybe_compile
def _hy_maybe_compile(compiler, source, filename, symbol):
"""The `codeop` version of this will compile the same source multiple
times, and, since we have macros and things like `eval-and-compile`, we
can't allow that.
"""
if not isinstance(compiler, HyCompile):
return _codeop_maybe_compile(compiler, source, filename, symbol)
for line in source.split("\n"):
line = line.strip()
if line and line[0] != ';':
# Leave it alone (could do more with Hy syntax)
break
else:
if symbol != "eval":
# Replace it with a 'pass' statement (i.e. tell the compiler to do
# nothing)
source = "pass"
return compiler(source, filename, symbol)
codeop._maybe_compile = _hy_maybe_compile
class HyCompile(codeop.Compile, object):
"""This compiler uses `linecache` like
`IPython.core.compilerop.CachingCompiler`.
"""
def __init__(self, module, locals, ast_callback=None,
hy_compiler=None, cmdline_cache={}):
self.module = module
self.locals = locals
self.ast_callback = ast_callback
self.hy_compiler = hy_compiler
super(HyCompile, self).__init__()
self.flags |= hy_ast_compile_flags
self.cmdline_cache = cmdline_cache
def _cache(self, source, name):
entry = (len(source),
time.time(),
[line + '\n' for line in source.splitlines()],
name)
linecache.cache[name] = entry
self.cmdline_cache[name] = entry
def _update_exc_info(self):
self.locals['_hy_last_type'] = sys.last_type
self.locals['_hy_last_value'] = sys.last_value
# Skip our frame.
sys.last_traceback = getattr(sys.last_traceback, 'tb_next',
sys.last_traceback)
self.locals['_hy_last_traceback'] = sys.last_traceback
def __call__(self, source, filename="<input>", symbol="single"):
if source == 'pass':
# We need to return a no-op to signal that no more input is needed.
return (compile(source, filename, symbol),) * 2
hash_digest = hashlib.sha1(source.encode("utf-8").strip()).hexdigest()
name = '{}-{}'.format(filename.strip('<>'), hash_digest)
try:
hy_ast = hy_parse(source, filename=name)
except Exception:
# Capture a traceback without the compiler/REPL frames.
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
self._update_exc_info()
raise
self._cache(source, name)
try:
hy_ast = hy_parse(source, filename=filename)
root_ast = ast.Interactive if symbol == 'single' else ast.Module
# Our compiler doesn't correspond to a real, fixed source file, so
# we need to [re]set these.
self.hy_compiler.filename = filename
self.hy_compiler.source = source
exec_ast, eval_ast = hy_compile(hy_ast, self.module, root=root_ast,
get_expr=True,
compiler=self.hy_compiler,
filename=filename, source=source)
if self.ast_callback:
self.ast_callback(exec_ast, eval_ast)
exec_code = super(HyCompile, self).__call__(exec_ast, name, symbol)
eval_code = super(HyCompile, self).__call__(eval_ast, name, 'eval')
except HyLanguageError:
# Hy will raise exceptions during compile-time that Python would
# raise during run-time (e.g. import errors for `require`). In
# order to work gracefully with the Python world, we convert such
# Hy errors to code that purposefully reraises those exceptions in
# the places where Python code expects them.
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
self._update_exc_info()
exec_code = super(HyCompile, self).__call__(
'import hy._compat; hy._compat.reraise('
'_hy_last_type, _hy_last_value, _hy_last_traceback)',
name, symbol)
eval_code = super(HyCompile, self).__call__('None', name, 'eval')
return exec_code, eval_code
class HyCommandCompiler(codeop.CommandCompiler, object):
def __init__(self, *args, **kwargs):
self.compiler = HyCompile(*args, **kwargs)
def __call__(self, *args, **kwargs):
try:
return super(HyCommandCompiler, self).__call__(*args, **kwargs)
except PrematureEndOfInput:
# We have to do this here, because `codeop._maybe_compile` won't
# take `None` for a return value (at least not in Python 2.7) and
# this exception type is also a `SyntaxError`, so it will be caught
# by `code.InteractiveConsole` base methods before it reaches our
# `runsource`.
return None
class HyREPL(code.InteractiveConsole, object):
def __init__(self, spy=False, output_fn=None, locals=None,
filename="<input>"):
filename="<stdin>"):
# Create a proper module for this REPL so that we can obtain it easily
# (e.g. using `importlib.import_module`).
# We let `InteractiveConsole` initialize `self.locals` when it's
# `None`.
super(HyREPL, self).__init__(locals=locals,
filename=filename)
module_name = self.locals.get('__name__', '__console__')
# Make sure our newly created module is properly introduced to
# `sys.modules`, and consistently use its namespace as `self.locals`
# from here on.
self.module = sys.modules.setdefault(module_name,
types.ModuleType(module_name))
self.module.__dict__.update(self.locals)
self.locals = self.module.__dict__
# Load cmdline-specific macros.
require('hy.cmdline', self.module, assignments='ALL')
self.hy_compiler = HyASTCompiler(self.module)
self.cmdline_cache = {}
self.compile = HyCommandCompiler(self.module,
self.locals,
ast_callback=self.ast_callback,
hy_compiler=self.hy_compiler,
cmdline_cache=self.cmdline_cache)
self.spy = spy
self.last_value = None
self.print_last_value = True
if output_fn is None:
self.output_fn = repr
@ -68,71 +269,102 @@ class HyREPL(code.InteractiveConsole):
module, f = '.'.join(parts[:-1]), parts[-1]
self.output_fn = getattr(importlib.import_module(module), f)
else:
self.output_fn = __builtins__[mangle(output_fn)]
code.InteractiveConsole.__init__(self, locals=locals,
filename=filename)
self.output_fn = getattr(builtins, mangle(output_fn))
# Pre-mangle symbols for repl recent results: *1, *2, *3
self._repl_results_symbols = [mangle("*{}".format(i + 1)) for i in range(3)]
self.locals.update({sym: None for sym in self._repl_results_symbols})
def runsource(self, source, filename='<input>', symbol='single'):
global SIMPLE_TRACEBACKS
# Allow access to the running REPL instance
self.locals['_hy_repl'] = self
def error_handler(e, use_simple_traceback=False):
self.locals[mangle("*e")] = e
if use_simple_traceback:
print(e, file=sys.stderr)
else:
self.showtraceback()
try:
def ast_callback(self, exec_ast, eval_ast):
if self.spy:
try:
do = import_buffer_to_hst(source)
except PrematureEndOfInput:
return True
except LexException as e:
if e.source is None:
e.source = source
e.filename = filename
error_handler(e, use_simple_traceback=True)
return False
# Mush the two AST chunks into a single module for
# conversion into Python.
new_ast = ast.Module(
exec_ast.body + [ast.Expr(eval_ast.body)],
type_ignores=[])
print(astor.to_source(new_ast))
except Exception:
msg = 'Exception in AST callback:\n{}\n'.format(
traceback.format_exc())
self.write(msg)
def _error_wrap(self, error_fn, exc_info_override=False, *args, **kwargs):
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
if exc_info_override:
# Use a traceback that doesn't have the REPL frames.
sys.last_type = self.locals.get('_hy_last_type', sys.last_type)
sys.last_value = self.locals.get('_hy_last_value', sys.last_value)
sys.last_traceback = self.locals.get('_hy_last_traceback',
sys.last_traceback)
# Sadly, this method in Python 2.7 ignores an overridden `sys.excepthook`.
if sys.excepthook is sys.__excepthook__:
error_fn(*args, **kwargs)
else:
sys.excepthook(sys.last_type, sys.last_value, sys.last_traceback)
self.locals[mangle("*e")] = sys.last_value
def showsyntaxerror(self, filename=None):
if filename is None:
filename = self.filename
self._error_wrap(super(HyREPL, self).showsyntaxerror,
exc_info_override=True,
filename=filename)
def showtraceback(self):
self._error_wrap(super(HyREPL, self).showtraceback)
def runcode(self, code):
try:
def ast_callback(main_ast, expr_ast):
if self.spy:
# Mush the two AST chunks into a single module for
# conversion into Python.
new_ast = ast.Module(main_ast.body +
[ast.Expr(expr_ast.body)])
print(astor.to_source(new_ast))
value = hy_eval(do, self.locals, "__console__",
ast_callback)
except HyTypeError as e:
if e.source is None:
e.source = source
e.filename = filename
error_handler(e, use_simple_traceback=SIMPLE_TRACEBACKS)
return False
eval(code[0], self.locals)
self.last_value = eval(code[1], self.locals)
# Don't print `None` values.
self.print_last_value = self.last_value is not None
except SystemExit:
raise
except Exception as e:
error_handler(e)
# Set this to avoid a print-out of the last value on errors.
self.print_last_value = False
self.showtraceback()
def runsource(self, source, filename='<stdin>', symbol='exec'):
try:
res = super(HyREPL, self).runsource(source, filename, symbol)
except (HyMacroExpansionError, HyRequireError):
# We need to handle these exceptions ourselves, because the base
# method only handles `OverflowError`, `SyntaxError` and
# `ValueError`.
self.showsyntaxerror(filename)
return False
except (HyLanguageError):
# Our compiler will also raise `TypeError`s
self.showtraceback()
return False
if value is not None:
# Shift exisitng REPL results
next_result = value
# Shift exisitng REPL results
if not res:
next_result = self.last_value
for sym in self._repl_results_symbols:
self.locals[sym], next_result = next_result, self.locals[sym]
# Print the value.
try:
output = self.output_fn(value)
except Exception as e:
error_handler(e)
return False
print(output)
return False
if self.print_last_value:
try:
output = self.output_fn(self.last_value)
except Exception:
self.showtraceback()
return False
print(output)
return res
@macro("koan")
@ -186,42 +418,18 @@ def ideas_macro(ETname):
""")])
require("hy.cmdline", "__console__", assignments="ALL")
require("hy.cmdline", "__main__", assignments="ALL")
SIMPLE_TRACEBACKS = True
def pretty_error(func, *args, **kw):
def run_command(source, filename=None):
__main__ = importlib.import_module('__main__')
require("hy.cmdline", __main__, assignments="ALL")
try:
return func(*args, **kw)
except (HyTypeError, LexException) as e:
if SIMPLE_TRACEBACKS:
print(e, file=sys.stderr)
sys.exit(1)
raise
tree = hy_parse(source, filename=filename)
except HyLanguageError:
hy_exc_handler(*sys.exc_info())
return 1
def run_command(source):
pretty_error(import_buffer_to_module, "__main__", source)
return 0
def run_module(mod_name):
from hy.importer import MetaImporter
pth = MetaImporter().find_on_path(mod_name)
if pth is not None:
sys.argv = [pth] + sys.argv
return run_file(pth)
print("{0}: module '{1}' not found.\n".format(hy.__appname__, mod_name),
file=sys.stderr)
return 1
def run_file(filename):
from hy.importer import import_file_to_module
pretty_error(import_file_to_module, "__main__", filename)
with filtered_hy_exceptions():
hy_eval(tree, None, __main__, filename=filename, source=source)
return 0
@ -230,13 +438,13 @@ def run_repl(hr=None, **kwargs):
sys.ps1 = "=> "
sys.ps2 = "... "
namespace = {'__name__': '__console__', '__doc__': ''}
with completion(Completer(namespace)):
if not hr:
hr = HyREPL(locals=namespace, **kwargs)
if not hr:
hr = HyREPL(**kwargs)
namespace = hr.locals
with filtered_hy_exceptions(), \
extend_linecache(hr.cmdline_cache), \
completion(Completer(namespace)):
hr.interact("{appname} {version} using "
"{py}({build}) {pyversion} on {os}".format(
appname=hy.__appname__,
@ -251,14 +459,29 @@ def run_repl(hr=None, **kwargs):
def run_icommand(source, **kwargs):
hr = HyREPL(**kwargs)
if os.path.exists(source):
with open(source, "r") as f:
# Emulate Python cmdline behavior by setting `sys.path` relative
# to the executed file's location.
if sys.path[0] == '':
sys.path[0] = os.path.realpath(os.path.split(source)[0])
else:
sys.path.insert(0, os.path.split(source)[0])
with io.open(source, "r", encoding='utf-8') as f:
source = f.read()
filename = source
else:
filename = '<input>'
hr.runsource(source, filename=filename, symbol='single')
filename = '<string>'
hr = HyREPL(**kwargs)
with filtered_hy_exceptions():
res = hr.runsource(source, filename=filename)
# If the command was prematurely ended, show an error (just like Python
# does).
if res:
hy_exc_handler(sys.last_type, sys.last_value, sys.last_traceback)
return run_repl(hr)
@ -284,6 +507,8 @@ def cmdline_handler(scriptname, argv):
help="module to run, passed in as a string")
parser.add_argument("-E", action='store_true',
help="ignore PYTHON* environment variables")
parser.add_argument("-B", action='store_true',
help="don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x")
parser.add_argument("-i", dest="icommand",
help="program passed in as a string, then stay in REPL")
parser.add_argument("--spy", action="store_true",
@ -293,20 +518,21 @@ def cmdline_handler(scriptname, argv):
"(e.g., hy.contrib.hy-repr.hy-repr)")
parser.add_argument("-v", "--version", action="version", version=VERSION)
parser.add_argument("--show-tracebacks", action="store_true",
help="show complete tracebacks for Hy exceptions")
# this will contain the script/program name and any arguments for it.
parser.add_argument('args', nargs=argparse.REMAINDER,
help=argparse.SUPPRESS)
# stash the hy executable in case we need it later
# mimics Python sys.executable
# Get the path of the Hy cmdline executable and swap it with
# `sys.executable` (saving the original, just in case).
# XXX: The `__main__` module will also have `__file__` set to the
# entry-point script. Currently, I don't see an immediate problem, but
# that's not how the Python cmdline works.
hy.executable = argv[0]
hy.sys_executable = sys.executable
sys.executable = hy.executable
# need to split the args if using "-m"
# all args after the MOD are sent to the module
# in sys.argv
# Need to split the args. If using "-m" all args after the MOD are sent to
# the module in sys.argv.
module_args = []
if "-m" in argv:
mloc = argv.index("-m")
@ -316,24 +542,22 @@ def cmdline_handler(scriptname, argv):
options = parser.parse_args(argv[1:])
if options.show_tracebacks:
global SIMPLE_TRACEBACKS
SIMPLE_TRACEBACKS = False
# reset sys.argv like Python
sys.argv = options.args + module_args or [""]
if options.E:
# User did "hy -E ..."
_remove_python_envs()
if options.B:
sys.dont_write_bytecode = True
if options.command:
# User did "hy -c ..."
return run_command(options.command)
return run_command(options.command, filename='<string>')
if options.mod:
# User did "hy -m ..."
return run_module(options.mod)
sys.argv = [sys.argv[0]] + options.args + module_args
runpy.run_module(options.mod, run_name='__main__', alter_sys=True)
return 0
if options.icommand:
# User did "hy -i ..."
@ -343,16 +567,31 @@ def cmdline_handler(scriptname, argv):
if options.args:
if options.args[0] == "-":
# Read the program from stdin
return run_command(sys.stdin.read())
return run_command(sys.stdin.read(), filename='<stdin>')
else:
# User did "hy <filename>"
filename = options.args[0]
# Emulate Python cmdline behavior by setting `sys.path` relative
# to the executed file's location.
if sys.path[0] == '':
sys.path[0] = os.path.realpath(os.path.split(filename)[0])
else:
sys.path.insert(0, os.path.split(filename)[0])
try:
return run_file(options.args[0])
except HyIOError as e:
print("hy: Can't open file '{0}': [Errno {1}] {2}\n".format(
e.filename, e.errno, e.strerror), file=sys.stderr)
sys.argv = options.args
with filtered_hy_exceptions():
runhy.run_path(filename, run_name='__main__')
return 0
except FileNotFoundError as e:
print("hy: Can't open file '{0}': [Errno {1}] {2}".format(
e.filename, e.errno, e.strerror), file=sys.stderr)
sys.exit(e.errno)
except HyLanguageError:
hy_exc_handler(*sys.exc_info())
sys.exit(1)
# User did NOTHING!
return run_repl(spy=options.spy, output_fn=options.repl_output_fn)
@ -360,33 +599,50 @@ def cmdline_handler(scriptname, argv):
# entry point for cmd line script "hy"
def hy_main():
sys.path.insert(0, "")
sys.exit(cmdline_handler("hy", sys.argv))
# entry point for cmd line script "hyc"
def hyc_main():
from hy.importer import write_hy_as_pyc
parser = argparse.ArgumentParser(prog="hyc")
parser.add_argument("files", metavar="FILE", nargs='+',
help="file to compile")
parser.add_argument("files", metavar="FILE", nargs='*',
help=('File(s) to compile (use STDIN if only'
' "-" or nothing is provided)'))
parser.add_argument("-v", action="version", version=VERSION)
options = parser.parse_args(sys.argv[1:])
for file in options.files:
try:
print("Compiling %s" % file)
pretty_error(write_hy_as_pyc, file)
except IOError as x:
print("hyc: Can't open file '{0}': [Errno {1}] {2}\n".format(
x.filename, x.errno, x.strerror), file=sys.stderr)
sys.exit(x.errno)
rv = 0
if len(options.files) == 0 or (
len(options.files) == 1 and options.files[0] == '-'):
while True:
filename = sys.stdin.readline()
if not filename:
break
filename = filename.rstrip('\n')
try:
py_compile.compile(filename, doraise=True)
except py_compile.PyCompileError as error:
rv = 1
sys.stderr.write("%s\n" % error.msg)
except OSError as error:
rv = 1
sys.stderr.write("%s\n" % error)
else:
for filename in options.files:
try:
print("Compiling %s" % filename)
py_compile.compile(filename, doraise=True)
except py_compile.PyCompileError as error:
# return value to indicate at least one failure
rv = 1
sys.stderr.write("%s\n" % error.msg)
return rv
# entry point for cmd line script "hy2py"
def hy2py_main():
import platform
module_name = "<STDIN>"
options = dict(prog="hy2py", usage="%(prog)s [options] [FILE]",
formatter_class=argparse.RawDescriptionHelpFormatter)
@ -404,17 +660,21 @@ def hy2py_main():
options = parser.parse_args(sys.argv[1:])
stdin_text = None
if options.FILE is None or options.FILE == '-':
stdin_text = sys.stdin.read()
filename = '<stdin>'
source = sys.stdin.read()
else:
filename = options.FILE
with io.open(options.FILE, 'r', encoding='utf-8') as source_file:
source = source_file.read()
with filtered_hy_exceptions():
hst = hy_parse(source, filename=filename)
if options.with_source:
hst = (pretty_error(import_file_to_hst, options.FILE)
if stdin_text is None
else pretty_error(import_buffer_to_hst, stdin_text))
# need special printing on Windows in case the
# codepage doesn't support utf-8 characters
if PY3 and platform.system() == "Windows":
if platform.system() == "Windows":
for h in hst:
try:
print(h)
@ -425,11 +685,11 @@ def hy2py_main():
print()
print()
_ast = (pretty_error(import_file_to_ast, options.FILE, module_name)
if stdin_text is None
else pretty_error(import_buffer_to_ast, stdin_text, module_name))
with filtered_hy_exceptions():
_ast = hy_compile(hst, '__main__', filename=filename, source=source)
if options.with_ast:
if PY3 and platform.system() == "Windows":
if platform.system() == "Windows":
_print_for_windows(astor.dump_tree(_ast))
else:
print(astor.dump_tree(_ast))
@ -437,7 +697,7 @@ def hy2py_main():
print()
if not options.without_python:
if PY3 and platform.system() == "Windows":
if platform.system() == "Windows":
_print_for_windows(astor.code_gen.to_source(_ast))
else:
print(astor.code_gen.to_source(_ast))

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
@ -6,10 +6,10 @@ import contextlib
import os
import re
import sys
import builtins
import hy.macros
import hy.compiler
from hy._compat import builtins, string_types
docomplete = True
@ -39,13 +39,15 @@ class Completer(object):
self.namespace = namespace
self.path = [hy.compiler._special_form_compilers,
builtins.__dict__,
hy.macros._hy_macros[None],
namespace]
self.tag_path = [hy.macros._hy_tag[None]]
if '__name__' in namespace:
module_name = namespace['__name__']
self.path.append(hy.macros._hy_macros[module_name])
self.tag_path.append(hy.macros._hy_tag[module_name])
self.tag_path = []
namespace.setdefault('__macros__', {})
namespace.setdefault('__tags__', {})
self.path.append(namespace['__macros__'])
self.tag_path.append(namespace['__tags__'])
def attr_matches(self, text):
# Borrowed from IPython's completer
@ -76,7 +78,7 @@ class Completer(object):
matches = []
for p in self.path:
for k in p.keys():
if isinstance(k, string_types):
if isinstance(k, str):
k = k.replace("_", "-")
if k.startswith(text):
matches.append(k)
@ -87,7 +89,7 @@ class Completer(object):
matches = []
for p in self.tag_path:
for k in p.keys():
if isinstance(k, string_types):
if isinstance(k, str):
if k.startswith(text):
matches.append("#{}".format(k))
return matches
@ -121,7 +123,7 @@ def completion(completer=None):
try:
readline.read_history_file(history)
except IOError:
open(history, 'a').close()
pass
readline.parse_and_bind(readline_bind)
@ -129,4 +131,7 @@ def completion(completer=None):
yield
finally:
if docomplete:
readline.write_history_file(history)
try:
readline.write_history_file(history)
except IOError:
pass

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -7,7 +7,7 @@
re
datetime
collections
[hy._compat [PY3 PY36 str-type bytes-type long-type]]
[hy._compat [PY36]]
[hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyFloat HyComplex HyList HyDict HySet HyString HyBytes]])
(try
@ -18,16 +18,17 @@
(setv -registry {})
(defn hy-repr-register [types f &optional placeholder]
(for [typ (if (instance? list types) types [types])]
(for [typ (if (list? types) types [types])]
(setv (get -registry typ) (, f placeholder))))
(setv -quoting False)
(setv -seen (set))
(defn hy-repr [obj]
(setv [f placeholder] (next
(genexpr (get -registry t)
[t (. (type obj) __mro__)]
(in t -registry))
(gfor
t (. (type obj) __mro__)
:if (in t -registry)
(get -registry t))
[-base-repr None]))
(global -quoting)
@ -55,18 +56,18 @@
; collections.namedtuple.)
(.format "({} {})"
(. (type x) __name__)
(.join " " (genexpr (+ ":" k " " (hy-repr v)) [[k v] (zip x._fields x)])))
(.join " " (gfor [k v] (zip x._fields x) (+ ":" k " " (hy-repr v)))))
; Otherwise, print it as a regular tuple.
(+ "(," (if x " " "") (-cat x) ")"))))
(hy-repr-register dict :placeholder "{...}" (fn [x]
(setv text (.join " " (genexpr
(+ (hy-repr k) " " (hy-repr v))
[[k v] (.items x)])))
(setv text (.join " " (gfor
[k v] (.items x)
(+ (hy-repr k) " " (hy-repr v)))))
(+ "{" text "}")))
(hy-repr-register HyDict :placeholder "{...}" (fn [x]
(setv text (.join " " (genexpr
(+ (hy-repr k) " " (hy-repr v))
[[k v] (partition x)])))
(setv text (.join " " (gfor
[k v] (partition x)
(+ (hy-repr k) " " (hy-repr v)))))
(if (% (len x) 2)
(+= text (+ " " (hy-repr (get x -1)))))
(+ "{" text "}")))
@ -83,10 +84,10 @@
(+ "(" (-cat x) ")"))))
(hy-repr-register [HySymbol HyKeyword] str)
(hy-repr-register [str-type bytes-type] (fn [x]
(hy-repr-register [str bytes] (fn [x]
(setv r (.lstrip (-base-repr x) "ub"))
(+
(if (instance? bytes-type x) "b" "")
(if (instance? bytes x) "b" "")
(if (.startswith "\"" r)
; If Python's built-in repr produced a double-quoted string, use
; that.
@ -95,10 +96,6 @@
; convert it.
(+ "\"" (.replace (cut r 1 -1) "\"" "\\\"") "\"")))))
(hy-repr-register bool str)
(if (not PY3) (hy-repr-register int (fn [x]
(.format "(int {})" (-base-repr x)))))
(if (not PY3) (hy-repr-register long_type (fn [x]
(.rstrip (-base-repr x) "L"))))
(hy-repr-register float (fn [x]
(if
(isnan x) "NaN"
@ -120,19 +117,23 @@
(hy-repr-register datetime.datetime (fn [x]
(.format "(datetime.datetime {}{})"
(.strftime x "%Y %-m %-d %-H %-M %-S")
(-strftime-0 x "%Y %m %d %H %M %S")
(-repr-time-innards x))))
(hy-repr-register datetime.date (fn [x]
(.strftime x "(datetime.date %Y %-m %-d)")))
(-strftime-0 x "(datetime.date %Y %m %d)")))
(hy-repr-register datetime.time (fn [x]
(.format "(datetime.time {}{})"
(.strftime x "%-H %-M %-S")
(-strftime-0 x "%H %M %S")
(-repr-time-innards x))))
(defn -repr-time-innards [x]
(.rstrip (+ " " (.join " " (filter identity [
(if x.microsecond (str-type x.microsecond))
(if x.microsecond (str x.microsecond))
(if (not (none? x.tzinfo)) (+ ":tzinfo " (hy-repr x.tzinfo)))
(if (and PY36 (!= x.fold 0)) (+ ":fold " (hy-repr x.fold)))])))))
(defn -strftime-0 [x fmt]
; Remove leading 0s in `strftime`. This is a substitute for the `-`
; flag for when Python isn't built with glibc.
(re.sub r"(\A| )0([0-9])" r"\1\2" (.strftime x fmt)))
(hy-repr-register collections.Counter (fn [x]
(.format "(Counter {})"
@ -143,7 +144,7 @@
(hy-repr (dict x)))))
(for [[types fmt] (partition [
list "[...]"
[list HyList] "[...]"
[set HySet] "#{...}"
frozenset "(frozenset #{...})"
dict-keys "(dict-keys [...])"
@ -162,5 +163,8 @@
; Call (.repr x) using the first class of x that doesn't inherit from
; HyObject.
(.__repr__
(next (genexpr t [t (. (type x) __mro__)] (not (issubclass t HyObject))))
(next (gfor
t (. (type x) __mro__)
:if (not (issubclass t HyObject))
t))
x))

View File

@ -1,5 +1,5 @@
;;; Hy tail-call optimization
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.

View File

@ -1,96 +0,0 @@
;; Hy Arity-overloading
;; Copyright 2018 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import [collections [defaultdict]]
[hy [HyExpression HyList HyString]])
(defclass MultiDispatch [object] [
_fns (defaultdict dict)
__init__ (fn [self f]
(setv self.f f)
(setv self.__doc__ f.__doc__)
(unless (in f.__name__ (.keys (get self._fns f.__module__)))
(setv (get self._fns f.__module__ f.__name__) {}))
(setv values f.__code__.co_varnames)
(setv (get self._fns f.__module__ f.__name__ values) f))
fn? (fn [self v args kwargs]
"Compare the given (checked fn) to the called fn"
(setv com (+ (list args) (list (.keys kwargs))))
(and
(= (len com) (len v))
(.issubset (frozenset (.keys kwargs)) com)))
__call__ (fn [self &rest args &kwargs kwargs]
(setv func None)
(for [[i f] (.items (get self._fns self.f.__module__ self.f.__name__))]
(when (.fn? self i args kwargs)
(setv func f)
(break)))
(if func
(func #* args #** kwargs)
(raise (TypeError "No matching functions with this signature"))))])
(defn multi-decorator [dispatch-fn]
(setv inner (fn [&rest args &kwargs kwargs]
(setv dispatch-key (dispatch-fn #* args #** kwargs))
(if (in dispatch-key inner.--multi--)
((get inner.--multi-- dispatch-key) #* args #** kwargs)
(inner.--multi-default-- #* args #** kwargs))))
(setv inner.--multi-- {})
(setv inner.--doc-- dispatch-fn.--doc--)
(setv inner.--multi-default-- (fn [&rest args &kwargs kwargs] None))
inner)
(defn method-decorator [dispatch-fn &optional [dispatch-key None]]
(setv apply-decorator
(fn [func]
(if (is dispatch-key None)
(setv dispatch-fn.--multi-default-- func)
(assoc dispatch-fn.--multi-- dispatch-key func))
dispatch-fn))
apply-decorator)
(defmacro defmulti [name params &rest body]
`(do (import [hy.contrib.multi [multi-decorator]])
(with-decorator multi-decorator
(defn ~name ~params ~@body))))
(defmacro defmethod [name multi-key params &rest body]
`(do (import [hy.contrib.multi [method-decorator]])
(with-decorator (method-decorator ~name ~multi-key)
(defn ~name ~params ~@body))))
(defmacro default-method [name params &rest body]
`(do (import [hy.contrib.multi [method-decorator]])
(with-decorator (method-decorator ~name)
(defn ~name ~params ~@body))))
(defn head-tail [l]
(, (get l 0) (cut l 1)))
(defmacro defn [name &rest bodies]
(setv arity-overloaded? (fn [bodies]
(if (isinstance (first bodies) HyString)
(arity-overloaded? (rest bodies))
(isinstance (first bodies) HyExpression))))
(if (arity-overloaded? bodies)
(do
(setv comment (HyString))
(if (= (type (first bodies)) HyString)
(setv [comment bodies] (head-tail bodies)))
(setv ret `(do))
(.append ret '(import [hy.contrib.multi [MultiDispatch]]))
(for [body bodies]
(setv [let-binds body] (head-tail body))
(.append ret
`(with-decorator MultiDispatch (defn ~name ~let-binds ~comment ~@body))))
ret)
(do
(setv [lambda-list body] (head-tail bodies))
`(setv ~name (fn* ~lambda-list ~@body)))))

View File

@ -1,5 +1,5 @@
;;; Hy profiling macros
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -19,9 +19,7 @@
`(do
(import cProfile pstats)
(if-python2
(import [StringIO [StringIO]])
(import [io [StringIO]]))
(import [io [StringIO]])
(setv ~g!hy-pr (.Profile cProfile))
(.enable ~g!hy-pr)

View File

@ -1,55 +1,62 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(defclass Sequence []
[--init-- (fn [self func]
"initialize a new sequence with a function to compute values"
(setv (. self func) func)
(setv (. self cache) [])
(setv (. self high-water) -1))
--getitem-- (fn [self n]
"get nth item of sequence"
(if (hasattr n "start")
(genexpr (get self x) [x (range n.start n.stop
(or n.step 1))])
(do (when (neg? n)
; Call (len) to force the whole
; sequence to be evaluated.
(len self))
(if (<= n (. self high-water))
(get (. self cache) n)
(do (while (< (. self high-water) n)
(setv (. self high-water) (inc (. self high-water)))
(.append (. self cache) (.func self (. self high-water))))
(get self n))))))
--iter-- (fn [self]
"create iterator for this sequence"
(setv index 0)
(try (while True
(yield (get self index))
(setv index (inc index)))
(except [IndexError]
(return))))
--len-- (fn [self]
"length of the sequence, dangerous for infinite sequences"
(setv index (. self high-water))
(try (while True
(get self index)
(setv index (inc index)))
(except [IndexError]
(len (. self cache)))))
max-items-in-repr 10
--str-- (fn [self]
"string representation of this sequence"
(setv items (list (take (inc self.max-items-in-repr) self)))
(.format (if (> (len items) self.max-items-in-repr)
"[{0}, ...]"
"[{0}]")
(.join ", " (map str items))))
--repr-- (fn [self]
"string representation of this sequence"
(.--str-- self))])
(defn --init-- [self func]
"initialize a new sequence with a function to compute values"
(setv (. self func) func)
(setv (. self cache) [])
(setv (. self high-water) -1))
(defn --getitem-- [self n]
"get nth item of sequence"
(if (hasattr n "start")
(gfor x (range n.start n.stop (or n.step 1))
(get self x))
(do (when (neg? n)
; Call (len) to force the whole
; sequence to be evaluated.
(len self))
(if (<= n (. self high-water))
(get (. self cache) n)
(do (while (< (. self high-water) n)
(setv (. self high-water) (inc (. self high-water)))
(.append (. self cache) (.func self (. self high-water))))
(get self n))))))
(defn --iter-- [self]
"create iterator for this sequence"
(setv index 0)
(try (while True
(yield (get self index))
(setv index (inc index)))
(except [IndexError]
(return))))
(defn --len-- [self]
"length of the sequence, dangerous for infinite sequences"
(setv index (. self high-water))
(try (while True
(get self index)
(setv index (inc index)))
(except [IndexError]
(len (. self cache)))))
(setv max-items-in-repr 10)
(defn --str-- [self]
"string representation of this sequence"
(setv items (list (take (inc self.max-items-in-repr) self)))
(.format (if (> (len items) self.max-items-in-repr)
"[{0}, ...]"
"[{0}]")
(.join ", " (map str items))))
(defn --repr-- [self]
"string representation of this sequence"
(.--str-- self)))
(defmacro seq [param &rest seq-code]
`(Sequence (fn ~param (do ~@seq-code))))

View File

@ -1,10 +1,12 @@
;;; Hy AST walker
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import [hy [HyExpression HyDict]]
[hy.models [HySequence]]
[functools [partial]]
[importlib [import-module]]
[collections [OrderedDict]]
[hy.macros [macroexpand :as mexpand]]
[hy.compiler [HyASTCompiler]])
@ -16,9 +18,7 @@
(cond
[(instance? HyExpression form)
(outer (HyExpression (map inner form)))]
[(instance? HyDict form)
(HyDict (outer (HyExpression (map inner form))))]
[(instance? list form)
[(or (instance? HySequence form) (list? form))
((type form) (outer (HyExpression (map inner form))))]
[(coll? form)
(walk inner outer (list form))]
@ -42,29 +42,37 @@
(defn macroexpand-all [form &optional module-name]
"Recursively performs all possible macroexpansions in form."
(setv module-name (or module-name (calling-module-name))
quote-level [0]) ; TODO: make nonlocal after dropping Python2
(setv module (or (and module-name
(import-module module-name))
(calling-module))
quote-level 0
ast-compiler (HyASTCompiler module)) ; TODO: make nonlocal after dropping Python2
(defn traverse [form]
(walk expand identity form))
(defn expand [form]
(nonlocal quote-level)
;; manages quote levels
(defn +quote [&optional [x 1]]
(nonlocal quote-level)
(setv head (first form))
(+= (get quote-level 0) x)
(when (neg? (get quote-level 0))
(+= quote-level x)
(when (neg? quote-level)
(raise (TypeError "unquote outside of quasiquote")))
(setv res (traverse (cut form 1)))
(-= (get quote-level 0) x)
(-= quote-level x)
`(~head ~@res))
(if (call? form)
(cond [(get quote-level 0)
(cond [quote-level
(cond [(in (first form) '[unquote unquote-splice])
(+quote -1)]
[(= (first form) 'quasiquote) (+quote)]
[True (traverse form)])]
[(= (first form) 'quote) form]
[(= (first form) 'quasiquote) (+quote)]
[True (traverse (mexpand form (HyASTCompiler module-name)))])
[(= (first form) (HySymbol "require"))
(ast-compiler.compile form)
(return)]
[True (traverse (mexpand form module ast-compiler))])
(if (coll? form)
(traverse form)
form)))
@ -82,7 +90,7 @@ splits a fn argument list into sections based on &-headers.
returns an OrderedDict mapping headers to sublists.
Arguments without a header are under None.
"
(setv headers '[&optional &rest &kwonly &kwargs]
(setv headers ['&optional '&rest '&kwonly '&kwargs]
sections (OrderedDict [(, None [])])
header None)
(for [arg form]
@ -162,7 +170,7 @@ Arguments without a header are under None.
#{})))))
(defn handle-args-list [self]
(setv protected #{}
argslist `[])
argslist [])
(for [[header section] (-> self (.tail) first lambda-list .items)]
(if header (.append argslist header))
(cond [(in header [None '&rest '&kwargs])
@ -295,7 +303,7 @@ But assignments via `import` are always hoisted to normal Python scope, and
likewise, `defclass` will assign the class to the Python scope,
even if it shares the name of a let binding.
Use __import__ and type (or whatever metaclass) instead,
Use `import_module` and `type` (or whatever metaclass) instead,
if you must avoid this hoisting.
Function arguments can shadow let bindings in their body,
@ -305,6 +313,7 @@ as can nested let forms.
(macro-error bindings "let bindings must be paired"))
(setv g!let (gensym 'let)
replacements (OrderedDict)
keys []
values [])
(defn expander [symbol]
(.get replacements symbol symbol))
@ -313,63 +322,14 @@ as can nested let forms.
(macro-error k "bind targets must be symbols")
(if (in '. k)
(macro-error k "binding target may not contain a dot")))
(.append values (symbolexpand (macroexpand-all v &name) expander))
(assoc replacements k `(get ~g!let ~(name k))))
(.append values (symbolexpand (macroexpand-all v &name)
expander))
(.append keys `(get ~g!let ~(name k)))
(assoc replacements k (last keys)))
`(do
(setv ~g!let {}
~@(interleave (.values replacements) values))
~@(symbolexpand (macroexpand-all body &name) expander)))
~@(interleave keys values))
~@(symbolexpand (macroexpand-all body &name)
expander)))
;; (defmacro macrolet [])
#_[special cases for let
;; Symbols containing a dot should be converted to this form.
;; attrs should not get expanded,
;; but [] lookups should.
'.',
;;; can shadow let bindings with Python locals
;; protect its bindings for the lexical scope of its body.
'fn',
'fn*',
;; protect as bindings for the lexical scope of its body
'except',
;;; changes scope of named variables
;; protect the variables they name for the lexical scope of their container
'global',
'nonlocal',
;; should we provide a protect form?
;; it's an anaphor only valid in a `let` body.
;; this would make the named variables python-scoped in its body
;; expands to a do
'protect',
;;; quoted variables must not be expanded.
;; but unprotected, unquoted variables must be.
'quasiquote',
'quote',
'unquote',
'unquote-splice',
;;;; deferred
;; should really only exist at toplevel. Ignore until someone complains?
;; raise an error? treat like fn?
;; should probably be implemented as macros in terms of fn/setv anyway.
'defmacro',
'deftag',
;;; create Python-scoped variables. It's probably hard to avoid this.
;; Best just doc this behavior for now.
;; we can't avoid clobbering enclosing python scope, unless we use a gensym,
;; but that corrupts '__name__'.
;; It could be set later, but that could mess up metaclasses!
;; Should the class name update let variables too?
'defclass',
;; should this update let variables?
;; it could be done with gensym/setv.
'import',
;; I don't understand these. Ignore until someone complains?
'eval_and_compile', 'eval_when_compile', 'require',]

View File

@ -1,5 +1,5 @@
;;; Hy bootstrap macros
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -14,15 +14,14 @@
(if* (not (isinstance macro-name hy.models.HySymbol))
(raise
(hy.errors.HyTypeError
macro-name
(% "received a `%s' instead of a symbol for macro name"
(. (type name)
__name__)))))
(for* [kw '[&kwonly &kwargs]]
(. (type name) __name__))
None --file-- None)))
(for [kw '[&kwonly &kwargs]]
(if* (in kw lambda-list)
(raise (hy.errors.HyTypeError macro-name
(% "macros cannot use %s"
kw)))))
(raise (hy.errors.HyTypeError (% "macros cannot use %s"
kw)
macro-name --file-- None))))
;; this looks familiar...
`(eval-and-compile
(import hy)
@ -45,12 +44,12 @@
(if (and (not (isinstance tag-name hy.models.HySymbol))
(not (isinstance tag-name hy.models.HyString)))
(raise (hy.errors.HyTypeError
tag-name
(% "received a `%s' instead of a symbol for tag macro name"
(. (type tag-name) __name__)))))
(. (type tag-name) --name--))
tag-name --file-- None)))
(if (or (= tag-name ":")
(= tag-name "&"))
(raise (NameError (% "%s can't be used as a tag macro name" tag-name))))
(raise (hy.errors.HyNameError (% "%s can't be used as a tag macro name" tag-name))))
(setv tag-name (.replace (hy.models.HyString tag-name)
tag-name))
`(eval-and-compile
@ -58,18 +57,15 @@
((hy.macros.tag ~tag-name)
(fn ~lambda-list ~@body))))
(defmacro macro-error [location reason]
"Error out properly within a macro at `location` giving `reason`."
`(raise (hy.errors.HyMacroExpansionError ~location ~reason)))
(defmacro macro-error [expression reason &optional [filename '--name--]]
`(raise (hy.errors.HyMacroExpansionError ~reason ~filename ~expression None)))
(defmacro defn [name lambda-list &rest body]
"Define `name` as a function with `lambda-list` signature and body `body`."
(defmacro defn [name &rest args]
"Define `name` as a function with `args` as the signature, annotations, and body."
(import hy)
(if (not (= (type name) hy.HySymbol))
(macro-error name "defn takes a name as first argument"))
(if (not (isinstance lambda-list hy.HyList))
(macro-error name "defn takes a parameter list as second argument"))
`(setv ~name (fn* ~lambda-list ~@body)))
`(setv ~name (fn* ~@args)))
(defmacro defn/a [name lambda-list &rest body]
"Define `name` as a function with `lambda-list` signature and body `body`."
@ -79,10 +75,3 @@
(if (not (isinstance lambda-list hy.HyList))
(macro-error name "defn/a takes a parameter list as second argument"))
`(setv ~name (fn/a ~lambda-list ~@body)))
(defmacro if-python2 [python2-form python3-form]
"If running on python2, execute python2-form, else, execute python3-form"
(import sys)
(if (< (get sys.version_info 0) 3)
python2-form
python3-form))

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -11,18 +11,15 @@
(import [fractions [Fraction :as fraction]])
(import operator) ; shadow not available yet
(import sys)
(if-python2
(import [StringIO [StringIO]])
(import [io [StringIO]]))
(import [hy._compat [long-type]]) ; long for python2, int for python3
(if-python2
(import [collections :as cabc])
(import [collections.abc :as cabc]))
(import [collections.abc :as cabc])
(import [hy.models [HySymbol HyKeyword]])
(import [hy.lex [LexException PrematureEndOfInput tokenize]])
(import [hy.lex.parser [mangle unmangle]])
(import [hy.compiler [HyASTCompiler]])
(import [hy.importer [hy-eval :as eval]])
(import [hy.lex [tokenize mangle unmangle read read-str]])
(import [hy.lex.exceptions [LexException PrematureEndOfInput]])
(import [hy.compiler [HyASTCompiler calling-module hy-eval :as eval]])
(import [hy.core.shadow [*]])
(require [hy.core.bootstrap [*]])
(defn butlast [coll]
"Return an iterator of all but the last item in `coll`."
@ -41,7 +38,7 @@
fs (tuple rfs))
(fn [&rest args &kwargs kwargs]
(setv res (first-f #* args #** kwargs))
(for* [f fs]
(for [f fs]
(setv res (f res)))
res))))
@ -79,53 +76,18 @@ If the second argument `codegen` is true, generate python code instead."
(defn distinct [coll]
"Return a generator from the original collection `coll` with no duplicates."
(setv seen (set) citer (iter coll))
(for* [val citer]
(for [val citer]
(if (not-in val seen)
(do
(yield val)
(.add seen val)))))
(if-python2
(setv
remove itertools.ifilterfalse
zip-longest itertools.izip_longest
;; not builtin in Python3
reduce reduce
;; hy is more like Python3
filter itertools.ifilter
input raw_input
map itertools.imap
range xrange
zip itertools.izip)
(setv
remove itertools.filterfalse
zip-longest itertools.zip_longest
;; was builtin in Python2
reduce functools.reduce
;; Someone can import these directly from `hy.core.language`;
;; we'll make some duplicates.
filter filter
input input
map map
range range
zip zip))
(if-python2
(defn exec [$code &optional $globals $locals]
"Execute Python code.
The parameter names contain weird characters to discourage calling this
function with keyword arguments, which isn't supported by Python 3's `exec`."
(if
(none? $globals) (do
(setv frame (._getframe sys (int 1)))
(try
(setv $globals frame.f_globals $locals frame.f_locals)
(finally (del frame))))
(none? $locals)
(setv $locals $globals))
(exec* $code $globals $locals))
(setv exec exec))
(setv
remove itertools.filterfalse
zip-longest itertools.zip_longest
;; was builtin in Python2
reduce functools.reduce
accumulate itertools.accumulate)
;; infinite iterators
(setv
@ -151,18 +113,6 @@ function with keyword arguments, which isn't supported by Python 3's `exec`."
permutations itertools.permutations
product itertools.product)
;; also from itertools, but not in Python2, and without func option until 3.3
(defn accumulate [iterable &optional [func operator.add]]
"Accumulate `func` on `iterable`.
Return series of accumulated sums (or other binary function results)."
(setv it (iter iterable)
total (next it))
(yield total)
(for* [element it]
(setv total (func total element))
(yield total)))
(defn drop [count coll]
"Drop `count` elements from `coll` and yield back the rest."
(islice coll count None))
@ -193,7 +143,7 @@ Return series of accumulated sums (or other binary function results)."
(defn _flatten [coll result]
(if (coll? coll)
(do (for* [b coll]
(do (for [b coll]
(_flatten b result)))
(.append result coll))
result)
@ -202,12 +152,18 @@ Return series of accumulated sums (or other binary function results)."
"Check if x is float."
(isinstance x float))
(defn list? [x]
(isinstance x list))
(defn tuple? [x]
(isinstance x tuple))
(defn symbol? [s]
"Check if `s` is a symbol."
(instance? HySymbol s))
(import [threading [Lock]])
(setv _gensym_counter 1234)
(setv _gensym_counter 0)
(setv _gensym_lock (Lock))
(defn gensym [&optional [g "G"]]
@ -217,7 +173,7 @@ Return series of accumulated sums (or other binary function results)."
(global _gensym_lock)
(.acquire _gensym_lock)
(try (do (setv _gensym_counter (inc _gensym_counter))
(setv new_symbol (HySymbol (.format "_;{0}|{1}" g _gensym_counter))))
(setv new_symbol (HySymbol (.format "_{}\uffff{}" g _gensym_counter))))
(finally (.release _gensym_lock)))
new_symbol)
@ -245,13 +201,9 @@ Return series of accumulated sums (or other binary function results)."
"Perform `isinstance` with reversed arguments."
(isinstance x klass))
(defn integer [x]
"Return Hy kind of integer for `x`."
(long-type x))
(defn integer? [x]
"Check if `x` is an integer."
(isinstance x (, int long-type)))
(isinstance x int))
(defn integer-char? [x]
"Check if char `x` parses as an integer."
@ -287,7 +239,7 @@ Return series of accumulated sums (or other binary function results)."
"Return a function applying each `fs` to args, collecting results in a list."
(setv fs (+ (, f) fs))
(fn [&rest args &kwargs kwargs]
(list-comp (f #* args #** kwargs) [f fs])))
(lfor f fs (f #* args #** kwargs))))
(defn last [coll]
"Return last item from `coll`."
@ -296,12 +248,14 @@ Return series of accumulated sums (or other binary function results)."
(defn macroexpand [form]
"Return the full macro expansion of `form`."
(import hy.macros)
(hy.macros.macroexpand form (HyASTCompiler (calling-module-name))))
(setv module (calling-module))
(hy.macros.macroexpand form module (HyASTCompiler module)))
(defn macroexpand-1 [form]
"Return the single step macro expansion of `form`."
(import hy.macros)
(hy.macros.macroexpand-1 form (HyASTCompiler (calling-module-name))))
(setv module (calling-module))
(hy.macros.macroexpand-1 form module (HyASTCompiler module)))
(defn merge-with [f &rest maps]
"Return the map of `maps` joined onto the first via the function `f`.
@ -352,8 +306,8 @@ with overlap."
(setv
step (or step n)
coll-clones (tee coll n)
slices (genexpr (islice (get coll-clones start) start None step)
[start (range n)]))
slices (gfor start (range n)
(islice (get coll-clones start) start None step)))
(if (is fillvalue -sentinel)
(zip #* slices)
(zip-longest #* slices :fillvalue fillvalue)))
@ -379,17 +333,9 @@ with overlap."
"Return the first logical true value of applying `pred` in `coll`, else None."
(first (filter None (map pred coll))))
(defn string [x]
"Cast `x` as the current python version's string implementation."
(if-python2
(unicode x)
(str x)))
(defn string? [x]
"Check if `x` is a string."
(if-python2
(isinstance x (, str unicode))
(isinstance x str)))
(isinstance x str))
(defn take [count coll]
"Take `count` elements from `coll`."
@ -402,9 +348,9 @@ Raises ValueError for (not (pos? n))."
(if (not (pos? n))
(raise (ValueError "n must be positive")))
(setv citer (iter coll) skip (dec n))
(for* [val citer]
(for [val citer]
(yield val)
(for* [_ (range skip)]
(for [_ (range skip)]
(try
(next citer)
(except [StopIteration]
@ -414,28 +360,6 @@ Raises ValueError for (not (pos? n))."
"Check if `n` equals 0."
(= n 0))
(defn read [&optional [from-file sys.stdin]
[eof ""]]
"Read from input and returns a tokenized string.
Can take a given input buffer to read from, and a single byte
as EOF (defaults to an empty string)."
(setv buff "")
(while True
(setv inn (string (.readline from-file)))
(if (= inn eof)
(raise (EOFError "Reached end of file")))
(+= buff inn)
(try
(setv parsed (first (tokenize buff)))
(except [e [PrematureEndOfInput IndexError]])
(else (break))))
parsed)
(defn read-str [input]
"Reads and tokenizes first line of `input`."
(read :from-file (StringIO input)))
(defn keyword [value]
"Create a keyword from `value`.
@ -446,7 +370,7 @@ Strings numbers and even objects with the __name__ magic will work."
(HyKeyword (unmangle value))
(try
(unmangle (.__name__ value))
(except [] (HyKeyword (string value)))))))
(except [] (HyKeyword (str value)))))))
(defn name [value]
"Convert `value` to a string.
@ -459,7 +383,7 @@ Even objects with the __name__ magic will work."
(unmangle value)
(try
(unmangle (. value __name__))
(except [] (string value))))))
(except [] (str value))))))
(defn xor [a b]
"Perform exclusive or between `a` and `b`."
@ -467,14 +391,27 @@ Even objects with the __name__ magic will work."
False
(or a b)))
(defn parse-args [spec &optional args &kwargs parser-args]
"Return arguments namespace parsed from `args` or `sys.argv` with `argparse.ArgumentParser.parse-args` according to `spec`.
`spec` should be a list of arguments to pass to repeated calls to
`argparse.ArgumentParser.add-argument`. `parser-args` may be a list
of keyword arguments to pass to the `argparse.ArgumentParser`
constructor."
(import argparse)
(setv parser (argparse.ArgumentParser #** parser-args))
(for [arg spec]
(eval `(.add-argument parser ~@arg)))
(.parse-args parser args))
(setv EXPORTS
'[*map accumulate butlast calling-module-name chain coll? combinations
comp complement compress constantly count cycle dec distinct
disassemble drop drop-last drop-while empty? eval even? every? exec first
filter flatten float? fraction gensym group-by identity inc input instance?
integer integer? integer-char? interleave interpose islice iterable?
iterate iterator? juxt keyword keyword? last macroexpand
macroexpand-1 mangle map merge-with multicombinations name neg? none? nth
numeric? odd? partition permutations pos? product range read read-str
remove repeat repeatedly rest reduce second some string string? symbol?
take take-nth take-while unmangle xor tee zero? zip zip-longest])
'[*map accumulate butlast calling-module calling-module-name chain coll?
combinations comp complement compress constantly count cycle dec distinct
disassemble drop drop-last drop-while empty? eval even? every? first
flatten float? fraction gensym group-by identity inc instance?
integer? integer-char? interleave interpose islice iterable?
iterate iterator? juxt keyword keyword? last list? macroexpand
macroexpand-1 mangle merge-with multicombinations name neg? none? nth
numeric? odd? parse-args partition permutations pos? product read read-str
remove repeat repeatedly rest reduce second some string? symbol?
take take-nth take-while tuple? unmangle xor tee zero? zip-longest])

View File

@ -1,14 +1,18 @@
;;; Hy core macros
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
;;; These macros form the hy language
;;; They are automatically required in every module, except inside hy.core
(import [hy.models [HyList HySymbol]])
(eval-and-compile
(import [hy.core.language [*]]))
(require [hy.core.bootstrap [*]])
(defmacro as-> [head name &rest rest]
"Beginning with `head`, expand a sequence of assignments `rest` to `name`.
@ -38,22 +42,20 @@ be associated in pairs."
`(setv ~@(+ (if other-kvs
[c coll]
[])
#* (genexpr [`(get ~c ~k) v]
[[k v] (partition (+ (, k1 v1)
other-kvs))]))))
#* (gfor [k v] (partition (+ (, k1 v1)
other-kvs))
[`(get ~c ~k) v]))))
(defn _with [node args body]
(if (not (empty? args))
(do
(if (>= (len args) 2)
(do
(setv p1 (.pop args 0)
p2 (.pop args 0)
primary [p1 p2])
`(~node [~@primary] ~(_with node args body)))
`(~node [~@args] ~@body)))
`(do ~@body)))
(if
(not args)
`(do ~@body)
(<= (len args) 2)
`(~node [~@args] ~@body)
True (do
(setv [p1 p2 #* args] args)
`(~node [~p1 ~p2] ~(_with node args body)))))
(defmacro with [args &rest body]
@ -83,59 +85,19 @@ Shorthand for nested with/a* loops:
The result in the bracket may be omitted, in which case the condition is also
used as the result."
(if (empty? branches)
None
(do
(setv branches (iter branches))
(setv branch (next branches))
(defn check-branch [branch]
"check `cond` branch for validity, return the corresponding `if` expr"
(if (not (= (type branch) HyList))
(macro-error branch "cond branches need to be a list"))
(if (< (len branch) 2)
(do
(setv g (gensym))
`(if (do (setv ~g ~(first branch)) ~g) ~g))
`(if ~(first branch) (do ~@(cut branch 1)))))
(or branches
(return))
(setv root (check-branch branch))
(setv latest-branch root)
(for* [branch branches]
(setv cur-branch (check-branch branch))
(.append latest-branch cur-branch)
(setv latest-branch cur-branch))
root)))
(defn _for [node args body]
(setv body (list body))
(setv belse (if (and body (isinstance (get body -1) HyExpression) (= (get body -1 0) "else"))
[(body.pop)]
[]))
(if
(odd? (len args)) (macro-error args "`for' requires an even number of args.")
(empty? args) `(do ~@body ~@belse)
(= (len args) 2) `(~node [~@args] (do ~@body) ~@belse)
(do
(setv alist (cut args 0 None 2))
`(~node [(, ~@alist) (genexpr (, ~@alist) [~@args])] (do ~@body) ~@belse))))
(defmacro for [args &rest body]
"Build a for-loop with `args` as a [element coll] bracket pair and run `body`.
Args may contain multiple pairs, in which case it executes a nested for-loop
in order of the given pairs."
(_for 'for* args body))
(defmacro for/a [args &rest body]
"Build a for/a-loop with `args` as a [element coll] bracket pair and run `body`.
Args may contain multiple pairs, in which case it executes a nested for/a-loop
in order of the given pairs."
(_for 'for/a* args body))
`(if ~@(reduce + (gfor
branch branches
(if
(not (and (is (type branch) hy.HyList) branch))
(macro-error branch "each cond branch needs to be a nonempty list")
(= (len branch) 1) (do
(setv g (gensym))
[`(do (setv ~g ~(first branch)) ~g) g])
True
[(first branch) `(do ~@(cut branch 1))])))))
(defmacro -> [head &rest args]
@ -144,7 +106,7 @@ in order of the given pairs."
The result of the first threaded form is inserted into the first position of
the second form, the second result is inserted into the third form, and so on."
(setv ret head)
(for* [node args]
(for [node args]
(setv ret (if (isinstance node HyExpression)
`(~(first node) ~ret ~@(rest node))
`(~node ~ret))))
@ -163,19 +125,34 @@ the second form, the second result is inserted into the third form, and so on."
~@(map build-form expressions)
~f))
(defmacro ->> [head &rest args]
"Thread `head` last through the `rest` of the forms.
The result of the first threaded form is inserted into the last position of
the second form, the second result is inserted into the third form, and so on."
(setv ret head)
(for* [node args]
(for [node args]
(setv ret (if (isinstance node HyExpression)
`(~@node ~ret)
`(~node ~ret))))
ret)
(defmacro of [base &rest args]
"Shorthand for indexing for type annotations.
If only one arguments are given, this expands to just that argument. If two arguments are
given, it expands to indexing the first argument via the second. Otherwise, the first argument
is indexed using a tuple of the rest.
E.g. `(of List int)` -> `List[int]`, `(of Dict str str)` -> `Dict[str, str]`."
(if
(empty? args) base
(= (len args) 1) `(get ~base ~@args)
`(get ~base (, ~@args))))
(defmacro if-not [test not-branch &optional yes-branch]
"Like `if`, but execute the first branch when the test fails"
`(if* (not ~test) ~not-branch ~yes-branch))
@ -210,7 +187,7 @@ the second form, the second result is inserted into the third form, and so on."
(defmacro with-gensyms [args &rest body]
"Execute `body` with `args` as bracket of names to gensym for use in macros."
(setv syms [])
(for* [arg args]
(for [arg args]
(.extend syms [arg `(gensym '~arg)]))
`(do
(setv ~@syms)
@ -225,7 +202,7 @@ the second form, the second result is inserted into the third form, and so on."
(.startswith x "g!")))
(flatten body))))
gensyms [])
(for* [sym syms]
(for [sym syms]
(.extend gensyms [sym `(gensym ~(cut sym 2))]))
`(defmacro ~name [~@args]
(setv ~@gensyms)
@ -235,25 +212,38 @@ the second form, the second result is inserted into the third form, and so on."
"Like `defmacro/g!`, with automatic once-only evaluation for 'o!' params.
Such 'o!' params are available within `body` as the equivalent 'g!' symbol."
(setv os (list-comp s [s args] (.startswith s "o!"))
gs (list-comp (HySymbol (+ "g!" (cut s 2))) [s os]))
(defn extract-o!-sym [arg]
(cond [(and (symbol? arg) (.startswith arg "o!"))
arg]
[(and (instance? HyList arg) (.startswith (first arg) "o!"))
(first arg)]))
(setv os (list (filter identity (map extract-o!-sym args)))
gs (lfor s os (HySymbol (+ "g!" (cut s 2)))))
`(defmacro/g! ~name ~args
`(do (setv ~@(interleave ~gs ~os))
~@~body)))
(defmacro defmain [args &rest body]
"Write a function named \"main\" and do the 'if __main__' dance"
(setv retval (gensym))
"Write a function named \"main\" and do the 'if __main__' dance.
The symbols in `args` are bound to the elements in `sys.argv`, which means that
the first symbol in `args` will always take the value of the script/executable
name (i.e. `sys.argv[0]`).
"
(setv retval (gensym)
restval (gensym))
`(when (= --name-- "__main__")
(import sys)
(setv ~retval ((fn [~@args] ~@body) #* sys.argv))
(setv ~retval ((fn [~@(or args `[&rest ~restval])] ~@body) #* sys.argv))
(if (integer? ~retval)
(sys.exit ~retval))))
(deftag @ [expr]
"with-decorator tag macro"
(if (empty? expr)
(macro-error expr "missing function argument"))
(setv decorators (cut expr None -1)
fndef (get expr -1))
`(with-decorator ~@decorators ~fndef))
@ -270,32 +260,10 @@ Such 'o!' params are available within `body` as the equivalent 'g!' symbol."
Use ``#doc foo`` instead for help with tag macro ``#foo``.
Use ``(help foo)`` instead for help with runtime objects."
`(try
(help (. (__import__ "hy")
macros
_hy_macros
[__name__]
['~symbol]))
(except [KeyError]
(help (. (__import__ "hy")
macros
_hy_macros
[None]
['~symbol])))))
`(help (.get __macros__ '~symbol None)))
(deftag doc [symbol]
"tag macro documentation
Gets help for a tag macro function available in this module."
`(try
(help (. (__import__ "hy")
macros
_hy_tag
[__name__]
['~symbol]))
(except [KeyError]
(help (. (__import__ "hy")
macros
_hy_tag
[None]
['~symbol])))))
`(help (.get __tags__ '~symbol None)))

View File

@ -1,14 +1,14 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
;;;; Hy shadow functions
(import operator)
(import [hy._compat [PY3 PY35]])
(if PY3
(import [functools [reduce]]))
(require [hy.core.bootstrap [*]])
(import [functools [reduce]])
(defn + [&rest args]
"Shadowed `+` operator adds `args`."
@ -58,10 +58,9 @@
"Shadowed `%` operator takes `x` modulo `y`."
(% x y))
(if PY35
(defn @ [a1 &rest a-rest]
"Shadowed `@` operator matrix multiples `a1` by each `a-rest`."
(reduce operator.matmul a-rest a1)))
(defn @ [a1 &rest a-rest]
"Shadowed `@` operator matrix multiples `a1` by each `a-rest`."
(reduce operator.matmul a-rest a1))
(defn << [a1 a2 &rest a-rest]
"Shadowed `<<` operator performs left-shift on `a1` by `a2`, ..., `a-rest`."
@ -98,8 +97,7 @@
(defn comp-op [op a1 a-rest]
"Helper for shadow comparison operators"
(if a-rest
(reduce (fn [x y] (and x y))
(list-comp (op x y) [(, x y) (zip (+ (, a1) a-rest) a-rest)]))
(and #* (gfor (, x y) (zip (+ (, a1) a-rest) a-rest) (op x y)))
True))
(defn < [a1 &rest a-rest]
"Shadowed `<` operator perform lt comparison on `a1` by each `a-rest`."
@ -161,7 +159,7 @@
(defn get [coll key1 &rest keys]
"Access item in `coll` indexed by `key1`, with optional `keys` nested-access."
(setv coll (get coll key1))
(for* [k keys]
(for [k keys]
(setv coll (get coll k)))
coll)
@ -172,5 +170,3 @@
'and 'or 'not
'is 'is-not 'in 'not-in
'get])
(if (not PY35)
(.remove EXPORTS '@))

View File

@ -1,104 +1,303 @@
# -*- encoding: utf-8 -*-
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
import os
import re
import sys
import traceback
import pkgutil
from clint.textui import colored
from functools import reduce
from colorama import Fore
from contextlib import contextmanager
from hy import _initialize_env_var
_hy_filter_internal_errors = _initialize_env_var('HY_FILTER_INTERNAL_ERRORS',
True)
COLORED = _initialize_env_var('HY_COLORED_ERRORS', False)
class HyError(Exception):
"""
Generic Hy error. All internal Exceptions will be subclassed from this
Exception.
"""
pass
class HyCompileError(HyError):
def __init__(self, exception, traceback=None):
self.exception = exception
self.traceback = traceback
class HyInternalError(HyError):
"""Unexpected errors occurring during compilation or parsing of Hy code.
def __str__(self):
if isinstance(self.exception, HyTypeError):
return str(self.exception)
if self.traceback:
tb = "".join(traceback.format_tb(self.traceback)).strip()
Errors sub-classing this are not intended to be user-facing, and will,
hopefully, never be seen by users!
"""
class HyLanguageError(HyError):
"""Errors caused by invalid use of the Hy language.
This, and any errors inheriting from this, are user-facing.
"""
def __init__(self, message, expression=None, filename=None, source=None,
lineno=1, colno=1):
"""
Parameters
----------
message: str
The message to display for this error.
expression: HyObject, optional
The Hy expression generating this error.
filename: str, optional
The filename for the source code generating this error.
Expression-provided information will take precedence of this value.
source: str, optional
The actual source code generating this error. Expression-provided
information will take precedence of this value.
lineno: int, optional
The line number of the error. Expression-provided information will
take precedence of this value.
colno: int, optional
The column number of the error. Expression-provided information
will take precedence of this value.
"""
self.msg = message
self.compute_lineinfo(expression, filename, source, lineno, colno)
if isinstance(self, SyntaxError):
syntax_error_args = (self.filename, self.lineno, self.offset,
self.text)
super(HyLanguageError, self).__init__(message, syntax_error_args)
else:
tb = "No traceback available. 😟"
return("Internal Compiler Bug 😱\n%s: %s\nCompilation traceback:\n%s"
% (self.exception.__class__.__name__,
self.exception, tb))
super(HyLanguageError, self).__init__(message)
def compute_lineinfo(self, expression, filename, source, lineno, colno):
class HyTypeError(TypeError):
def __init__(self, expression, message):
super(HyTypeError, self).__init__(message)
self.expression = expression
self.message = message
self.source = None
self.filename = None
# NOTE: We use `SyntaxError`'s field names (i.e. `text`, `offset`,
# `msg`) for compatibility and print-outs.
self.text = getattr(expression, 'source', source)
self.filename = getattr(expression, 'filename', filename)
def __str__(self):
if self.text:
lines = self.text.splitlines()
result = ""
self.lineno = getattr(expression, 'start_line', lineno)
self.offset = getattr(expression, 'start_column', colno)
end_column = getattr(expression, 'end_column',
len(lines[self.lineno-1]))
end_line = getattr(expression, 'end_line', self.lineno)
if all(getattr(self.expression, x, None) is not None
for x in ("start_line", "start_column", "end_column")):
# Trim the source down to the essentials.
self.text = '\n'.join(lines[self.lineno-1:end_line])
line = self.expression.start_line
start = self.expression.start_column
end = self.expression.end_column
source = []
if self.source is not None:
source = self.source.split("\n")[line-1:self.expression.end_line]
if line == self.expression.end_line:
length = end - start
if end_column:
if self.lineno == end_line:
self.arrow_offset = end_column
else:
length = len(source[0]) - start
result += ' File "%s", line %d, column %d\n\n' % (self.filename,
line,
start)
if len(source) == 1:
result += ' %s\n' % colored.red(source[0])
result += ' %s%s\n' % (' '*(start-1),
colored.green('^' + '-'*(length-1) + '^'))
if len(source) > 1:
result += ' %s\n' % colored.red(source[0])
result += ' %s%s\n' % (' '*(start-1),
colored.green('^' + '-'*length))
if len(source) > 2: # write the middle lines
for line in source[1:-1]:
result += ' %s\n' % colored.red("".join(line))
result += ' %s\n' % colored.green("-"*len(line))
# write the last line
result += ' %s\n' % colored.red("".join(source[-1]))
result += ' %s\n' % colored.green('-'*(end-1) + '^')
self.arrow_offset = len(self.text[0])
self.arrow_offset -= self.offset
else:
self.arrow_offset = None
else:
result += ' File "%s", unknown location\n' % self.filename
# We could attempt to extract the source given a filename, but we
# don't.
self.lineno = lineno
self.offset = colno
self.arrow_offset = None
result += colored.yellow("%s: %s\n\n" %
(self.__class__.__name__,
self.message))
def __str__(self):
"""Provide an exception message that includes SyntaxError-like source
line information when available.
"""
# Syntax errors are special and annotate the traceback (instead of what
# we would do in the message that follows the traceback).
if isinstance(self, SyntaxError):
return super(HyLanguageError, self).__str__()
# When there isn't extra source information, use the normal message.
elif not self.text:
return super(HyLanguageError, self).__str__()
return result
# Re-purpose Python's builtin syntax error formatting.
output = traceback.format_exception_only(
SyntaxError,
SyntaxError(self.msg, (self.filename, self.lineno, self.offset,
self.text)))
arrow_idx, _ = next(((i, x) for i, x in enumerate(output)
if x.strip() == '^'),
(None, None))
if arrow_idx:
msg_idx = arrow_idx + 1
else:
msg_idx, _ = next((i, x) for i, x in enumerate(output)
if x.startswith('SyntaxError: '))
# Get rid of erroneous error-type label.
output[msg_idx] = re.sub('^SyntaxError: ', '', output[msg_idx])
# Extend the text arrow, when given enough source info.
if arrow_idx and self.arrow_offset:
output[arrow_idx] = '{}{}^\n'.format(output[arrow_idx].rstrip('\n'),
'-' * (self.arrow_offset - 1))
if COLORED:
output[msg_idx:] = [Fore.YELLOW + o + Fore.RESET for o in output[msg_idx:]]
if arrow_idx:
output[arrow_idx] = Fore.GREEN + output[arrow_idx] + Fore.RESET
for idx, line in enumerate(output[::msg_idx]):
if line.strip().startswith(
'File "{}", line'.format(self.filename)):
output[idx] = Fore.RED + line + Fore.RESET
# This resulting string will come after a "<class-name>:" prompt, so
# put it down a line.
output.insert(0, '\n')
# Avoid "...expected str instance, ColoredString found"
return reduce(lambda x, y: x + y, output)
class HyMacroExpansionError(HyTypeError):
pass
class HyCompileError(HyInternalError):
"""Unexpected errors occurring within the compiler."""
class HyIOError(HyError, IOError):
class HyTypeError(HyLanguageError, TypeError):
"""TypeError occurring during the normal use of Hy."""
class HyNameError(HyLanguageError, NameError):
"""NameError occurring during the normal use of Hy."""
class HyRequireError(HyLanguageError):
"""Errors arising during the use of `require`
This, and any errors inheriting from this, are user-facing.
"""
Trivial subclass of IOError and HyError, to distinguish between
IOErrors raised by Hy itself as opposed to Hy programs.
class HyMacroExpansionError(HyLanguageError):
"""Errors caused by invalid use of Hy macros.
This, and any errors inheriting from this, are user-facing.
"""
pass
class HyEvalError(HyLanguageError):
"""Errors occurring during code evaluation at compile-time.
These errors distinguish unexpected errors within the compilation process
(i.e. `HyInternalError`s) from unrelated errors in user code evaluated by
the compiler (e.g. in `eval-and-compile`).
This, and any errors inheriting from this, are user-facing.
"""
class HyIOError(HyInternalError, IOError):
""" Subclass used to distinguish between IOErrors raised by Hy itself as
opposed to Hy programs.
"""
class HySyntaxError(HyLanguageError, SyntaxError):
"""Error during the Lexing of a Hython expression."""
class HyWrapperError(HyError, TypeError):
"""Errors caused by language model object wrapping.
These can be caused by improper user-level use of a macro, so they're
not really "internal". If they arise due to anything else, they're an
internal/compiler problem, though.
"""
def _module_filter_name(module_name):
try:
compiler_loader = pkgutil.get_loader(module_name)
if not compiler_loader:
return None
filename = compiler_loader.get_filename(module_name)
if not filename:
return None
if compiler_loader.is_package(module_name):
# Use the package directory (e.g. instead of `.../__init__.py`) so
# that we can filter all modules in a package.
return os.path.dirname(filename)
else:
# Normalize filename endings, because tracebacks will use `pyc` when
# the loader says `py`.
return filename.replace('.pyc', '.py')
except Exception:
return None
_tb_hidden_modules = {m for m in map(_module_filter_name,
['hy.compiler', 'hy.lex',
'hy.cmdline', 'hy.lex.parser',
'hy.importer', 'hy._compat',
'hy.macros', 'hy.models',
'rply'])
if m is not None}
def hy_exc_filter(exc_type, exc_value, exc_traceback):
"""Produce exceptions print-outs with all frames originating from the
modules in `_tb_hidden_modules` filtered out.
The frames are actually filtered by each module's filename and only when a
subclass of `HyLanguageError` is emitted.
This does not remove the frames from the actual tracebacks, so debugging
will show everything.
"""
# frame = (filename, line number, function name*, text)
new_tb = []
for frame in traceback.extract_tb(exc_traceback):
if not (frame[0].replace('.pyc', '.py') in _tb_hidden_modules or
os.path.dirname(frame[0]) in _tb_hidden_modules):
new_tb += [frame]
lines = traceback.format_list(new_tb)
lines.insert(0, "Traceback (most recent call last):\n")
lines.extend(traceback.format_exception_only(exc_type, exc_value))
output = ''.join(lines)
return output
def hy_exc_handler(exc_type, exc_value, exc_traceback):
"""A `sys.excepthook` handler that uses `hy_exc_filter` to
remove internal Hy frames from a traceback print-out.
"""
if os.environ.get('HY_DEBUG', False):
return sys.__excepthook__(exc_type, exc_value, exc_traceback)
try:
output = hy_exc_filter(exc_type, exc_value, exc_traceback)
sys.stderr.write(output)
sys.stderr.flush()
except Exception:
sys.__excepthook__(exc_type, exc_value, exc_traceback)
@contextmanager
def filtered_hy_exceptions():
"""Temporarily apply a `sys.excepthook` that filters Hy internal frames
from tracebacks.
Filtering can be controlled by the variable
`hy.errors._hy_filter_internal_errors` and environment variable
`HY_FILTER_INTERNAL_ERRORS`.
"""
global _hy_filter_internal_errors
if _hy_filter_internal_errors:
current_hook = sys.excepthook
sys.excepthook = hy_exc_handler
yield
sys.excepthook = current_hook
else:
yield

View File

@ -1,5 +1,5 @@
;;; Hy anaphoric macros
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -109,18 +109,18 @@
`%*` and `%**` name the `&rest` and `&kwargs` parameters, respectively.
Nesting of `#%` forms is not recommended."
(setv %symbols (set-comp a
[a (flatten [expr])]
(and (symbol? a)
(.startswith a '%))))
(setv %symbols (sfor a (flatten [expr])
:if (and (symbol? a)
(.startswith a '%))
a))
`(fn [;; generate all %i symbols up to the maximum found in expr
~@(genexpr (HySymbol (+ "%" (str i)))
[i (range 1 (-> (list-comp (int (cut a 1))
[a %symbols]
(.isdigit (cut a 1)))
(or (, 0))
max
inc))])
~@(gfor i (range 1 (-> (lfor a %symbols
:if (.isdigit (cut a 1))
(int (cut a 1)))
(or (, 0))
max
inc))
(HySymbol (+ "%" (str i))))
;; generate the &rest parameter only if '%* is present in expr
~@(if (in '%* %symbols)
'(&rest %*))

View File

@ -1,5 +1,5 @@
;;; Get a frozenset of Hy reserved words
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -16,7 +16,7 @@
(setv _cache (frozenset (map unmangle (+
hy.core.language.EXPORTS
hy.core.shadow.EXPORTS
(list (.keys (get hy.macros._hy_macros None)))
(list (.keys hy.core.macros.__macros__))
keyword.kwlist
(list (.keys hy.compiler._special_form_compilers))
(list hy.compiler._bad_roots)))))))

View File

@ -1,287 +1,167 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
from __future__ import absolute_import
from hy.compiler import hy_compile, HyTypeError
from hy.models import HyObject, HyExpression, HySymbol
from hy.lex import tokenize, LexException
from hy.errors import HyIOError
from io import open
import re
import marshal
import struct
import imp
import sys
import ast
import inspect
import os
import __future__
import inspect
import pkgutil
import re
import io
import types
import tempfile
import importlib
from hy._compat import PY3, PY37, MAGIC, builtins, long_type, wr_long
from hy._compat import string_types
from functools import partial
from contextlib import contextmanager
from hy.compiler import hy_compile, hy_ast_compile_flags
from hy.lex import hy_parse
def ast_compile(ast, filename, mode):
"""Compile AST.
Like Python's compile, but with some special flags."""
flags = (__future__.CO_FUTURE_DIVISION |
__future__.CO_FUTURE_PRINT_FUNCTION)
return compile(ast, filename, mode, flags)
@contextmanager
def loader_module_obj(loader):
"""Use the module object associated with a loader.
This is intended to be used by a loader object itself, and primarily as a
work-around for attempts to get module and/or file code from a loader
without actually creating a module object. Since Hy currently needs the
module object for macro importing, expansion, and whatnot, using this will
reconcile Hy with such attempts.
def import_buffer_to_hst(buf):
"""Import content from buf and return a Hy AST."""
return HyExpression([HySymbol("do")] + tokenize(buf + "\n"))
For example, if we're first compiling a Hy script starting from
`runpy.run_path`, the Hy compiler will need a valid module object in which
to run, but, given the way `runpy.run_path` works, there might not be one
yet (e.g. `__main__` for a .hy file). We compensate by properly loading
the module here.
The function `inspect.getmodule` has a hidden-ish feature that returns
modules using their associated filenames (via `inspect.modulesbyfile`),
and, since the Loaders (and their delegate Loaders) carry a filename/path
associated with the parent package, we use it as a more robust attempt to
obtain an existing module object.
When no module object is found, a temporary, minimally sufficient module
object is created for the duration of the `with` body.
"""
tmp_mod = False
def import_file_to_hst(fpath):
"""Import content from fpath and return a Hy AST."""
try:
with open(fpath, 'r', encoding='utf-8') as f:
buf = f.read()
# Strip the shebang line, if there is one.
buf = re.sub(r'\A#!.*', '', buf)
return import_buffer_to_hst(buf)
except IOError as e:
raise HyIOError(e.errno, e.strerror, e.filename)
module = inspect.getmodule(None, _filename=loader.path)
except KeyError:
module = None
if module is None:
tmp_mod = True
module = sys.modules.setdefault(loader.name,
types.ModuleType(loader.name))
module.__file__ = loader.path
module.__name__ = loader.name
def import_buffer_to_ast(buf, module_name):
""" Import content from buf and return a Python AST."""
return hy_compile(import_buffer_to_hst(buf), module_name)
def import_file_to_ast(fpath, module_name):
"""Import content from fpath and return a Python AST."""
return hy_compile(import_file_to_hst(fpath), module_name)
def import_file_to_module(module_name, fpath, loader=None):
"""Import Hy source from fpath and put it into a Python module.
If there's an up-to-date byte-compiled version of this module, load that
instead. Otherwise, byte-compile the module once we're done loading it, if
we can.
Return the module."""
module = None
bytecode_path = get_bytecode_path(fpath)
try:
source_mtime = int(os.stat(fpath).st_mtime)
with open(bytecode_path, 'rb') as bc_f:
# The first 4 bytes are the magic number for the version of Python
# that compiled this bytecode.
bytecode_magic = bc_f.read(4)
# Python 3.7 introduced a new flags entry in the header structure.
if PY37:
bc_f.read(4)
# The next 4 bytes, interpreted as a little-endian 32-bit integer,
# are the mtime of the corresponding source file.
bytecode_mtime, = struct.unpack('<i', bc_f.read(4))
except (IOError, OSError):
pass
else:
if bytecode_magic == MAGIC and bytecode_mtime >= source_mtime:
# It's a cache hit. Load the byte-compiled version.
if PY3:
# As of Python 3.6, imp.load_compiled still exists, but it's
# deprecated. So let's use SourcelessFileLoader instead.
from importlib.machinery import SourcelessFileLoader
module = (SourcelessFileLoader(module_name, bytecode_path).
load_module(module_name))
else:
module = imp.load_compiled(module_name, bytecode_path)
if not module:
# It's a cache miss, so load from source.
sys.modules[module_name] = None
try:
_ast = import_file_to_ast(fpath, module_name)
module = imp.new_module(module_name)
module.__file__ = os.path.normpath(fpath)
code = ast_compile(_ast, fpath, "exec")
if not os.environ.get('PYTHONDONTWRITEBYTECODE'):
try:
write_code_as_pyc(fpath, code)
except (IOError, OSError):
# We failed to save the bytecode, probably because of a
# permissions issue. The user only asked to import the
# file, so don't bug them about it.
pass
eval(code, module.__dict__)
except (HyTypeError, LexException) as e:
if e.source is None:
with open(fpath, 'rt') as fp:
e.source = fp.read()
e.filename = fpath
raise
except Exception:
sys.modules.pop(module_name, None)
raise
sys.modules[module_name] = module
module.__name__ = module_name
module.__file__ = os.path.normpath(fpath)
if loader:
module.__loader__ = loader
if is_package(module_name):
module.__path__ = []
module.__package__ = module_name
else:
module.__package__ = module_name.rpartition('.')[0]
return module
yield module
finally:
if tmp_mod:
del sys.modules[loader.name]
def import_buffer_to_module(module_name, buf):
def _hy_code_from_file(filename, loader_type=None):
"""Use PEP-302 loader to produce code for a given Hy source file."""
full_fname = os.path.abspath(filename)
fname_path, fname_file = os.path.split(full_fname)
modname = os.path.splitext(fname_file)[0]
sys.path.insert(0, fname_path)
try:
_ast = import_buffer_to_ast(buf, module_name)
mod = imp.new_module(module_name)
eval(ast_compile(_ast, "", "exec"), mod.__dict__)
except (HyTypeError, LexException) as e:
if e.source is None:
e.source = buf
e.filename = '<stdin>'
raise
if loader_type is None:
loader = pkgutil.get_loader(modname)
else:
loader = loader_type(modname, full_fname)
code = loader.get_code(modname)
finally:
sys.path.pop(0)
return code
def _get_code_from_file(run_name, fname=None,
hy_src_check=lambda x: x.endswith('.hy')):
"""A patch of `runpy._get_code_from_file` that will also run and cache Hy
code.
"""
if fname is None and run_name is not None:
fname = run_name
# Check for bytecode first. (This is what the `runpy` version does!)
with open(fname, "rb") as f:
code = pkgutil.read_code(f)
if code is None:
if hy_src_check(fname):
code = _hy_code_from_file(fname, loader_type=HyLoader)
else:
# Try normal source
with open(fname, "rb") as f:
# This code differs from `runpy`'s only in that we
# force decoding into UTF-8.
source = f.read().decode('utf-8')
code = compile(source, fname, 'exec')
return (code, fname)
importlib.machinery.SOURCE_SUFFIXES.insert(0, '.hy')
_py_source_to_code = importlib.machinery.SourceFileLoader.source_to_code
def _could_be_hy_src(filename):
return (os.path.isfile(filename) and
(filename.endswith('.hy') or
not any(filename.endswith(ext)
for ext in importlib.machinery.SOURCE_SUFFIXES[1:])))
def _hy_source_to_code(self, data, path, _optimize=-1):
if _could_be_hy_src(path):
source = data.decode("utf-8")
hy_tree = hy_parse(source, filename=path)
with loader_module_obj(self) as module:
data = hy_compile(hy_tree, module)
return _py_source_to_code(self, data, path, _optimize=_optimize)
importlib.machinery.SourceFileLoader.source_to_code = _hy_source_to_code
# This is actually needed; otherwise, pre-created finders assigned to the
# current dir (i.e. `''`) in `sys.path` will not catch absolute imports of
# directory-local modules!
sys.path_importer_cache.clear()
# Do this one just in case?
importlib.invalidate_caches()
# XXX: These aren't truly cross-compliant.
# They're useful for testing, though.
HyImporter = importlib.machinery.FileFinder
HyLoader = importlib.machinery.SourceFileLoader
# We create a separate version of runpy, "runhy", that prefers Hy source over
# Python.
runhy = importlib.import_module('runpy')
runhy._get_code_from_file = partial(_get_code_from_file,
hy_src_check=_could_be_hy_src)
del sys.modules['runpy']
runpy = importlib.import_module('runpy')
_runpy_get_code_from_file = runpy._get_code_from_file
runpy._get_code_from_file = _get_code_from_file
def _import_from_path(name, path):
"""A helper function that imports a module from the given path."""
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def hy_eval(hytree, namespace=None, module_name=None, ast_callback=None):
"""``eval`` evaluates a quoted expression and returns the value. The optional
second and third arguments specify the dictionary of globals to use and the
module name. The globals dictionary defaults to ``(local)`` and the module
name defaults to the name of the current module.
=> (eval '(print "Hello World"))
"Hello World"
If you want to evaluate a string, use ``read-str`` to convert it to a
form first:
=> (eval (read-str "(+ 1 1)"))
2"""
if namespace is None:
frame = inspect.stack()[1][0]
namespace = inspect.getargvalues(frame).locals
if module_name is None:
m = inspect.getmodule(inspect.stack()[1][0])
module_name = '__eval__' if m is None else m.__name__
if not isinstance(module_name, string_types):
raise TypeError("Module name must be a string")
_ast, expr = hy_compile(hytree, module_name, get_expr=True)
# Spoof the positions in the generated ast...
for node in ast.walk(_ast):
node.lineno = 1
node.col_offset = 1
for node in ast.walk(expr):
node.lineno = 1
node.col_offset = 1
if ast_callback:
ast_callback(_ast, expr)
if not isinstance(namespace, dict):
raise TypeError("Globals must be a dictionary")
# Two-step eval: eval() the body of the exec call
eval(ast_compile(_ast, "<eval_body>", "exec"), namespace)
# Then eval the expression context and return that
return eval(ast_compile(expr, "<eval>", "eval"), namespace)
def write_hy_as_pyc(fname):
_ast = import_file_to_ast(fname,
os.path.basename(os.path.splitext(fname)[0]))
code = ast_compile(_ast, fname, "exec")
write_code_as_pyc(fname, code)
def write_code_as_pyc(fname, code):
st = os.stat(fname)
timestamp = long_type(st.st_mtime)
cfile = get_bytecode_path(fname)
try:
os.makedirs(os.path.dirname(cfile))
except (IOError, OSError):
pass
with builtins.open(cfile, 'wb') as fc:
fc.write(MAGIC)
if PY37:
# With PEP 552, the header structure has a new flags field
# that we need to fill in. All zeros preserve the legacy
# behaviour, but should we implement reproducible builds,
# this is where we'd add the information.
wr_long(fc, 0)
wr_long(fc, timestamp)
if PY3:
wr_long(fc, st.st_size)
marshal.dump(code, fc)
class MetaLoader(object):
def __init__(self, path):
self.path = path
def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]
if not self.path:
return
return import_file_to_module(fullname, self.path, self)
class MetaImporter(object):
def find_on_path(self, fullname):
fls = ["%s/__init__.hy", "%s.hy"]
dirpath = "/".join(fullname.split("."))
for pth in sys.path:
pth = os.path.abspath(pth)
for fp in fls:
composed_path = fp % ("%s/%s" % (pth, dirpath))
if os.path.exists(composed_path):
return composed_path
def find_module(self, fullname, path=None):
path = self.find_on_path(fullname)
if path:
return MetaLoader(path)
sys.meta_path.insert(0, MetaImporter())
sys.path.insert(0, "")
def is_package(module_name):
mpath = os.path.join(*module_name.split("."))
for path in map(os.path.abspath, sys.path):
if os.path.exists(os.path.join(path, mpath, "__init__.hy")):
return True
return False
def get_bytecode_path(source_path):
if PY3:
import importlib.util
return importlib.util.cache_from_source(source_path)
elif hasattr(imp, "cache_from_source"):
return imp.cache_from_source(source_path)
else:
# If source_path has a file extension, replace it with ".pyc".
# Otherwise, just append ".pyc".
d, f = os.path.split(source_path)
return os.path.join(d, re.sub(r"(?:\.[^.]+)?\Z", ".pyc", f))

View File

@ -1,37 +0,0 @@
# Copyright 2018 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
from __future__ import absolute_import
import inspect
try:
# Check if we have the newer inspect.signature available.
# Otherwise fallback to the legacy getargspec.
inspect.signature # noqa
except AttributeError:
def get_arity(fn):
return len(inspect.getargspec(fn)[0])
def has_kwargs(fn):
argspec = inspect.getargspec(fn)
return argspec.keywords is not None
def format_args(fn):
argspec = inspect.getargspec(fn)
return inspect.formatargspec(*argspec)
else:
def get_arity(fn):
parameters = inspect.signature(fn).parameters
return sum(1 for param in parameters.values()
if param.kind == param.POSITIONAL_OR_KEYWORD)
def has_kwargs(fn):
parameters = inspect.signature(fn).parameters
return any(param.kind == param.VAR_KEYWORD
for param in parameters.values())
def format_args(fn):
return str(inspect.signature(fn))

View File

@ -1,25 +1,201 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
from rply.errors import LexingError
from __future__ import unicode_literals
from hy.lex.exceptions import LexException, PrematureEndOfInput # NOQA
from hy.lex.lexer import lexer
from hy.lex.parser import parser
import keyword
import re
import sys
import unicodedata
from hy.lex.exceptions import PrematureEndOfInput, LexException # NOQA
from hy.models import HyExpression, HySymbol
try:
from io import StringIO
except ImportError:
from StringIO import StringIO
def tokenize(buf):
def hy_parse(source, filename='<string>'):
"""Parse a Hy source string.
Parameters
----------
source: string
Source code to parse.
filename: string, optional
File name corresponding to source. Defaults to "<string>".
Returns
-------
out : HyExpression
"""
Tokenize a Lisp file or string buffer into internal Hy objects.
_source = re.sub(r'\A#!.*', '', source)
res = HyExpression([HySymbol("do")] +
tokenize(_source + "\n",
filename=filename))
res.source = source
res.filename = filename
return res
class ParserState(object):
def __init__(self, source, filename):
self.source = source
self.filename = filename
def tokenize(source, filename=None):
""" Tokenize a Lisp file or string buffer into internal Hy objects.
Parameters
----------
source: str
The source to tokenize.
filename: str, optional
The filename corresponding to `source`.
"""
from hy.lex.lexer import lexer
from hy.lex.parser import parser
from rply.errors import LexingError
try:
return parser.parse(lexer.lex(buf))
return parser.parse(lexer.lex(source),
state=ParserState(source, filename))
except LexingError as e:
pos = e.getsourcepos()
raise LexException("Could not identify the next token.",
pos.lineno, pos.colno, buf)
None, filename, source,
max(pos.lineno, 1),
max(pos.colno, 1))
except LexException as e:
if e.source is None:
e.source = buf
raise
raise e
def parse_one_thing(src_string):
"""Parse the first form from the string. Return it and the
remainder of the string."""
import re
from hy.lex.lexer import lexer
from hy.lex.parser import parser
from rply.errors import LexingError
tokens = []
err = None
for token in lexer.lex(src_string):
tokens.append(token)
try:
model, = parser.parse(
iter(tokens),
state=ParserState(src_string, filename=None))
except (LexingError, LexException) as e:
err = e
else:
return model, src_string[re.match(
r'.+\n' * (model.end_line - 1)
+ '.' * model.end_column,
src_string).end():]
if err:
raise err
raise ValueError("No form found")
mangle_delim = 'X'
def mangle(s):
"""Stringify the argument and convert it to a valid Python identifier
according to Hy's mangling rules."""
def unicode_char_to_hex(uchr):
# Covert a unicode char to hex string, without prefix
if len(uchr) == 1 and ord(uchr) < 128:
return format(ord(uchr), 'x')
return (uchr.encode('unicode-escape').decode('utf-8')
.lstrip('\\U').lstrip('\\u').lstrip('\\x').lstrip('0'))
assert s
s = str(s)
s = s.replace("-", "_")
s2 = s.lstrip('_')
leading_underscores = '_' * (len(s) - len(s2))
s = s2
if s.endswith("?"):
s = 'is_' + s[:-1]
if not isidentifier(leading_underscores + s):
# Replace illegal characters with their Unicode character
# names, or hexadecimal if they don't have one.
s = 'hyx_' + ''.join(
c
if c != mangle_delim and isidentifier('S' + c)
# We prepend the "S" because some characters aren't
# allowed at the start of an identifier.
else '{0}{1}{0}'.format(mangle_delim,
unicodedata.name(c, '').lower().replace('-', 'H').replace(' ', '_')
or 'U{}'.format(unicode_char_to_hex(c)))
for c in s)
s = leading_underscores + s
assert isidentifier(s)
return s
def unmangle(s):
"""Stringify the argument and try to convert it to a pretty unmangled
form. This may not round-trip, because different Hy symbol names can
mangle to the same Python identifier."""
s = str(s)
s2 = s.lstrip('_')
leading_underscores = len(s) - len(s2)
s = s2
if s.startswith('hyx_'):
s = re.sub('{0}(U)?([_a-z0-9H]+?){0}'.format(mangle_delim),
lambda mo:
chr(int(mo.group(2), base=16))
if mo.group(1)
else unicodedata.lookup(
mo.group(2).replace('_', ' ').replace('H', '-').upper()),
s[len('hyx_'):])
if s.startswith('is_'):
s = s[len("is_"):] + "?"
s = s.replace('_', '-')
return '-' * leading_underscores + s
def read(from_file=sys.stdin, eof=""):
"""Read from input and returns a tokenized string.
Can take a given input buffer to read from, and a single byte as EOF
(defaults to an empty string).
"""
buff = ""
while True:
inn = str(from_file.readline())
if inn == eof:
raise EOFError("Reached end of file")
buff += inn
try:
parsed = next(iter(tokenize(buff)), None)
except (PrematureEndOfInput, IndexError):
pass
else:
break
return parsed
def read_str(input):
return read(StringIO(str(input)))
def isidentifier(x):
if x in ('True', 'False', 'None'):
return True
if keyword.iskeyword(x):
return False
return x.isidentifier()

View File

@ -1,49 +1,39 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
from hy.errors import HyError
from hy.errors import HySyntaxError
class LexException(HyError):
"""Error during the Lexing of a Hython expression."""
def __init__(self, message, lineno, colno, source=None):
super(LexException, self).__init__(message)
self.message = message
self.lineno = lineno
self.colno = colno
self.source = source
self.filename = '<stdin>'
class LexException(HySyntaxError):
def __str__(self):
from hy.errors import colored
@classmethod
def from_lexer(cls, message, state, token):
lineno = None
colno = None
source = state.source
source_pos = token.getsourcepos()
line = self.lineno
start = self.colno
if source_pos:
lineno = source_pos.lineno
colno = source_pos.colno
elif source:
# Use the end of the last line of source for `PrematureEndOfInput`.
# We get rid of empty lines and spaces so that the error matches
# with the last piece of visible code.
lines = source.rstrip().splitlines()
lineno = lineno or len(lines)
colno = colno or len(lines[lineno - 1])
else:
lineno = lineno or 1
colno = colno or 1
result = ""
source = self.source.split("\n")
if line > 0 and start > 0:
result += ' File "%s", line %d, column %d\n\n' % (self.filename,
line,
start)
if len(self.source) > 0:
source_line = source[line-1]
else:
source_line = ""
result += ' %s\n' % colored.red(source_line)
result += ' %s%s\n' % (' '*(start-1), colored.green('^'))
result += colored.yellow("LexException: %s\n\n" % self.message)
return result
return cls(message,
None,
state.filename,
source,
lineno,
colno)
class PrematureEndOfInput(LexException):
"""We got a premature end of input"""
def __init__(self, message):
super(PrematureEndOfInput, self).__init__(message, -1, -1)
pass

View File

@ -1,4 +1,4 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
@ -10,7 +10,8 @@ lg = LexerGenerator()
# A regexp for something that should end a quoting/unquoting operator
# i.e. a space or a closing brace/paren/curly
end_quote = r'(?![\s\)\]\}])'
end_quote_set = r'\s\)\]\}'
end_quote = r'(?![%s])' % end_quote_set
identifier = r'[^()\[\]{}\'"\s;]+'
@ -25,6 +26,7 @@ lg.add('QUOTE', r'\'%s' % end_quote)
lg.add('QUASIQUOTE', r'`%s' % end_quote)
lg.add('UNQUOTESPLICE', r'~@%s' % end_quote)
lg.add('UNQUOTE', r'~%s' % end_quote)
lg.add('ANNOTATION', r'\^(?![=%s])' % end_quote_set)
lg.add('DISCARD', r'#_')
lg.add('HASHSTARS', r'#\*+')
lg.add('BRACKETSTRING', r'''(?x)
@ -38,7 +40,7 @@ lg.add('HASHOTHER', r'#%s' % identifier)
# A regexp which matches incomplete strings, used to support
# multi-line strings in the interpreter
partial_string = r'''(?x)
(?:u|r|ur|ru|b|br|rb)? # prefix
(?:u|r|ur|ru|b|br|rb|f|fr|rf)? # prefix
" # start string
(?:
| [^"\\] # non-quote or backslash

View File

@ -1,16 +1,14 @@
# -*- encoding: utf-8 -*-
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
from __future__ import unicode_literals
from functools import wraps
import string, re, unicodedata
from rply import ParserGenerator
from hy._compat import PY3, str_type, isidentifier, UCS4
from hy.models import (HyBytes, HyComplex, HyDict, HyExpression, HyFloat,
HyInteger, HyKeyword, HyList, HySet, HyString, HySymbol)
from .lexer import lexer
@ -19,104 +17,33 @@ from .exceptions import LexException, PrematureEndOfInput
pg = ParserGenerator([rule.name for rule in lexer.rules] + ['$end'])
mangle_delim = 'X'
def unicode_to_ucs4iter(ustr):
# Covert a unicode string to an iterable object,
# elements in the object are single USC-4 unicode characters
if UCS4:
return ustr
ucs4_list = list(ustr)
for i, u in enumerate(ucs4_list):
if 0xD7FF < ord(u) < 0xDC00:
ucs4_list[i] += ucs4_list[i + 1]
del ucs4_list[i + 1]
return ucs4_list
def mangle(s):
"""Stringify the argument and convert it to a valid Python identifier
according to Hy's mangling rules."""
def unicode_char_to_hex(uchr):
# Covert a unicode char to hex string, without prefix
return uchr.encode('unicode-escape').decode('utf-8').lstrip('\\U').lstrip('\\u').lstrip('0')
assert s
s = str_type(s)
s = s.replace("-", "_")
s2 = s.lstrip('_')
leading_underscores = '_' * (len(s) - len(s2))
s = s2
if s.endswith("?"):
s = 'is_' + s[:-1]
if not isidentifier(leading_underscores + s):
# Replace illegal characters with their Unicode character
# names, or hexadecimal if they don't have one.
s = 'hyx_' + ''.join(
c
if c != mangle_delim and isidentifier('S' + c)
# We prepend the "S" because some characters aren't
# allowed at the start of an identifier.
else '{0}{1}{0}'.format(mangle_delim,
unicodedata.name(c, '').lower().replace('-', 'H').replace(' ', '_')
or 'U{}'.format(unicode_char_to_hex(c)))
for c in unicode_to_ucs4iter(s))
s = leading_underscores + s
assert isidentifier(s)
return s
def unmangle(s):
"""Stringify the argument and try to convert it to a pretty unmangled
form. This may not round-trip, because different Hy symbol names can
mangle to the same Python identifier."""
s = str_type(s)
s2 = s.lstrip('_')
leading_underscores = len(s) - len(s2)
s = s2
if s.startswith('hyx_'):
s = re.sub('{0}(U)?([_a-z0-9H]+?){0}'.format(mangle_delim),
lambda mo:
chr(int(mo.group(2), base=16))
if mo.group(1)
else unicodedata.lookup(
mo.group(2).replace('_', ' ').replace('H', '-').upper()),
s[len('hyx_'):])
if s.startswith('is_'):
s = s[len("is_"):] + "?"
s = s.replace('_', '-')
return '-' * leading_underscores + s
def set_boundaries(fun):
@wraps(fun)
def wrapped(p):
def wrapped(state, p):
start = p[0].source_pos
end = p[-1].source_pos
ret = fun(p)
ret = fun(state, p)
ret.start_line = start.lineno
ret.start_column = start.colno
if start is not end:
ret.end_line = end.lineno
ret.end_column = end.colno
else:
ret.end_line = start.lineno
ret.end_column = start.colno + len(p[0].value)
v = p[0].value
ret.end_line = start.lineno + v.count('\n')
ret.end_column = (len(v) - v.rindex('\n') - 1
if '\n' in v
else start.colno + len(v) - 1)
return ret
return wrapped
def set_quote_boundaries(fun):
@wraps(fun)
def wrapped(p):
def wrapped(state, p):
start = p[0].source_pos
ret = fun(p)
ret = fun(state, p)
ret.start_line = start.lineno
ret.start_column = start.colno
ret.end_line = p[-1].end_line
@ -126,54 +53,45 @@ def set_quote_boundaries(fun):
@pg.production("main : list_contents")
def main(p):
def main(state, p):
return p[0]
@pg.production("main : $end")
def main_empty(p):
def main_empty(state, p):
return []
def reject_spurious_dots(*items):
"Reject the spurious dots from items"
for list in items:
for tok in list:
if tok == "." and type(tok) == HySymbol:
raise LexException("Malformed dotted list",
tok.start_line, tok.start_column)
@pg.production("paren : LPAREN list_contents RPAREN")
@set_boundaries
def paren(p):
def paren(state, p):
return HyExpression(p[1])
@pg.production("paren : LPAREN RPAREN")
@set_boundaries
def empty_paren(p):
def empty_paren(state, p):
return HyExpression([])
@pg.production("list_contents : term list_contents")
def list_contents(p):
def list_contents(state, p):
return [p[0]] + p[1]
@pg.production("list_contents : term")
def list_contents_single(p):
def list_contents_single(state, p):
return [p[0]]
@pg.production("list_contents : DISCARD term discarded_list_contents")
def list_contents_empty(p):
def list_contents_empty(state, p):
return []
@pg.production("discarded_list_contents : DISCARD term discarded_list_contents")
@pg.production("discarded_list_contents :")
def discarded_list_contents(p):
def discarded_list_contents(state, p):
pass
@ -183,58 +101,64 @@ def discarded_list_contents(p):
@pg.production("term : list")
@pg.production("term : set")
@pg.production("term : string")
def term(p):
def term(state, p):
return p[0]
@pg.production("term : DISCARD term term")
def term_discard(p):
def term_discard(state, p):
return p[2]
@pg.production("term : QUOTE term")
@set_quote_boundaries
def term_quote(p):
def term_quote(state, p):
return HyExpression([HySymbol("quote"), p[1]])
@pg.production("term : QUASIQUOTE term")
@set_quote_boundaries
def term_quasiquote(p):
def term_quasiquote(state, p):
return HyExpression([HySymbol("quasiquote"), p[1]])
@pg.production("term : UNQUOTE term")
@set_quote_boundaries
def term_unquote(p):
def term_unquote(state, p):
return HyExpression([HySymbol("unquote"), p[1]])
@pg.production("term : UNQUOTESPLICE term")
@set_quote_boundaries
def term_unquote_splice(p):
def term_unquote_splice(state, p):
return HyExpression([HySymbol("unquote-splice"), p[1]])
@pg.production("term : ANNOTATION term")
@set_quote_boundaries
def term_annotation(state, p):
return HyExpression([HySymbol("annotate*"), p[1]])
@pg.production("term : HASHSTARS term")
@set_quote_boundaries
def term_hashstars(p):
def term_hashstars(state, p):
n_stars = len(p[0].getstr()[1:])
if n_stars == 1:
sym = "unpack-iterable"
elif n_stars == 2:
sym = "unpack-mapping"
else:
raise LexException(
raise LexException.from_lexer(
"Too many stars in `#*` construct (if you want to unpack a symbol "
"beginning with a star, separate it with whitespace)",
p[0].source_pos.lineno, p[0].source_pos.colno)
state, p[0])
return HyExpression([HySymbol(sym), p[1]])
@pg.production("term : HASHOTHER term")
@set_quote_boundaries
def hash_other(p):
def hash_other(state, p):
# p == [(Token('HASHOTHER', '#foo'), bar)]
st = p[0].getstr()[1:]
str_object = HyString(st)
@ -244,71 +168,82 @@ def hash_other(p):
@pg.production("set : HLCURLY list_contents RCURLY")
@set_boundaries
def t_set(p):
def t_set(state, p):
return HySet(p[1])
@pg.production("set : HLCURLY RCURLY")
@set_boundaries
def empty_set(p):
def empty_set(state, p):
return HySet([])
@pg.production("dict : LCURLY list_contents RCURLY")
@set_boundaries
def t_dict(p):
def t_dict(state, p):
return HyDict(p[1])
@pg.production("dict : LCURLY RCURLY")
@set_boundaries
def empty_dict(p):
def empty_dict(state, p):
return HyDict([])
@pg.production("list : LBRACKET list_contents RBRACKET")
@set_boundaries
def t_list(p):
def t_list(state, p):
return HyList(p[1])
@pg.production("list : LBRACKET RBRACKET")
@set_boundaries
def t_empty_list(p):
def t_empty_list(state, p):
return HyList([])
@pg.production("string : STRING")
@set_boundaries
def t_string(p):
def t_string(state, p):
s = p[0].value
# Detect and remove any "f" prefix.
is_format = False
if s.startswith('f') or s.startswith('rf'):
is_format = True
s = s.replace('f', '', 1)
# Replace the single double quotes with triple double quotes to allow
# embedded newlines.
try:
s = eval(p[0].value.replace('"', '"""', 1)[:-1] + '"""')
s = eval(s.replace('"', '"""', 1)[:-1] + '"""')
except SyntaxError:
raise LexException("Can't convert {} to a HyString".format(p[0].value),
p[0].source_pos.lineno, p[0].source_pos.colno)
return (HyString if isinstance(s, str_type) else HyBytes)(s)
raise LexException.from_lexer("Can't convert {} to a HyString".format(p[0].value),
state, p[0])
return (HyString(s, is_format = is_format)
if isinstance(s, str)
else HyBytes(s))
@pg.production("string : PARTIAL_STRING")
def t_partial_string(p):
def t_partial_string(state, p):
# Any unterminated string requires more input
raise PrematureEndOfInput("Premature end of input")
raise PrematureEndOfInput.from_lexer("Partial string literal", state, p[0])
bracket_string_re = next(r.re for r in lexer.rules if r.name == 'BRACKETSTRING')
@pg.production("string : BRACKETSTRING")
@set_boundaries
def t_bracket_string(p):
def t_bracket_string(state, p):
m = bracket_string_re.match(p[0].value)
delim, content = m.groups()
return HyString(content, brackets=delim)
return HyString(
content,
is_format = delim == 'f' or delim.startswith('f-'),
brackets = delim)
@pg.production("identifier : IDENTIFIER")
@set_boundaries
def t_identifier(p):
def t_identifier(state, p):
obj = p[0].value
val = symbol_like(obj)
@ -317,11 +252,11 @@ def t_identifier(p):
if "." in obj and symbol_like(obj.split(".", 1)[0]) is not None:
# E.g., `5.attr` or `:foo.attr`
raise LexException(
raise LexException.from_lexer(
'Cannot access attribute on anything other than a name (in '
'order to get attributes of expressions, use '
'`(. <expression> <attr>)` or `(.<attr> <expression>)`)',
p[0].source_pos.lineno, p[0].source_pos.colno)
state, p[0])
return HySymbol(obj)
@ -358,14 +293,15 @@ def symbol_like(obj):
@pg.error
def error_handler(token):
def error_handler(state, token):
tokentype = token.gettokentype()
if tokentype == '$end':
raise PrematureEndOfInput("Premature end of input")
raise PrematureEndOfInput.from_lexer("Premature end of input", state,
token)
else:
raise LexException(
"Ran into a %s where it wasn't expected." % tokentype,
token.source_pos.lineno, token.source_pos.colno)
raise LexException.from_lexer(
"Ran into a %s where it wasn't expected." % tokentype, state,
token)
parser = pg.build()

View File

@ -1,16 +1,42 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
import sys
import importlib
import inspect
import pkgutil
import traceback
from hy._compat import PY3
import hy.inspect
from contextlib import contextmanager
from hy._compat import reraise, PY38
from hy.models import replace_hy_obj, HyExpression, HySymbol, wrap_value
from hy.lex.parser import mangle
from hy._compat import str_type
from hy.lex import mangle
from hy.errors import (HyLanguageError, HyMacroExpansionError, HyTypeError,
HyRequireError)
from hy.errors import HyTypeError, HyMacroExpansionError
try:
# Check if we have the newer inspect.signature available.
# Otherwise fallback to the legacy getargspec.
inspect.signature # noqa
except AttributeError:
def has_kwargs(fn):
argspec = inspect.getargspec(fn)
return argspec.keywords is not None
def format_args(fn):
argspec = inspect.getargspec(fn)
return inspect.formatargspec(*argspec)
else:
def has_kwargs(fn):
parameters = inspect.signature(fn).parameters
return any(param.kind == param.VAR_KEYWORD
for param in parameters.values())
def format_args(fn):
return str(inspect.signature(fn))
from collections import defaultdict
CORE_MACROS = [
"hy.core.bootstrap",
@ -20,150 +46,268 @@ EXTRA_MACROS = [
"hy.core.macros",
]
_hy_macros = defaultdict(dict)
_hy_tag = defaultdict(dict)
def macro(name):
"""Decorator to define a macro called `name`.
This stores the macro `name` in the namespace for the module where it is
defined.
If the module where it is defined is in `hy.core`, then the macro is stored
in the default `None` namespace.
This function is called from the `defmacro` special form in the compiler.
"""
name = mangle(name)
def _(fn):
fn.__name__ = '({})'.format(name)
fn = rename_function(fn, name)
try:
fn._hy_macro_pass_compiler = hy.inspect.has_kwargs(fn)
fn._hy_macro_pass_compiler = has_kwargs(fn)
except Exception:
# An exception might be raised if fn has arguments with
# names that are invalid in Python.
fn._hy_macro_pass_compiler = False
module_name = fn.__module__
if module_name.startswith("hy.core"):
module_name = None
_hy_macros[module_name][name] = fn
module = inspect.getmodule(fn)
module_macros = module.__dict__.setdefault('__macros__', {})
module_macros[name] = fn
return fn
return _
def tag(name):
"""Decorator to define a tag macro called `name`.
This stores the macro `name` in the namespace for the module where it is
defined.
If the module where it is defined is in `hy.core`, then the macro is stored
in the default `None` namespace.
This function is called from the `deftag` special form in the compiler.
"""
def _(fn):
_name = mangle('#{}'.format(name))
if not PY3:
_name = _name.encode('UTF-8')
fn.__name__ = _name
module_name = fn.__module__
fn = rename_function(fn, _name)
module = inspect.getmodule(fn)
module_name = module.__name__
if module_name.startswith("hy.core"):
module_name = None
_hy_tag[module_name][mangle(name)] = fn
module_tags = module.__dict__.setdefault('__tags__', {})
module_tags[mangle(name)] = fn
return fn
return _
def _same_modules(source_module, target_module):
"""Compare the filenames associated with the given modules names.
This tries to not actually load the modules.
"""
if not (source_module or target_module):
return False
if target_module == source_module:
return True
def _get_filename(module):
filename = None
try:
if not inspect.ismodule(module):
loader = pkgutil.get_loader(module)
if loader:
filename = loader.get_filename()
else:
filename = inspect.getfile(module)
except (TypeError, ImportError):
pass
return filename
source_filename = _get_filename(source_module)
target_filename = _get_filename(target_module)
return (source_filename and target_filename and
source_filename == target_filename)
def require(source_module, target_module, assignments, prefix=""):
"""Load macros from `source_module` in the namespace of
`target_module`. `assignments` maps old names to new names, or
should be the string "ALL". 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.)
"""Load macros from one module into the namespace of another.
This function is called from the `require` special form in the compiler.
"""
Parameters
----------
source_module: str or types.ModuleType
The module from which macros are to be imported.
target_module: str, types.ModuleType or None
The module into which the macros will be loaded. If `None`, then
the caller's namespace.
The latter is useful during evaluation of generated AST/bytecode.
assignments: str or list of tuples of strs
The string "ALL" or a list of macro name and alias pairs.
prefix: str, optional ("")
If nonempty, its value is prepended to the name of each imported macro.
This allows one to emulate namespaced macros, like
"mymacromodule.mymacro", which looks like an attribute of a module.
Returns
-------
out: boolean
Whether or not macros and tags were actually transferred.
"""
if target_module is None:
parent_frame = inspect.stack()[1][0]
target_namespace = parent_frame.f_globals
target_module = target_namespace.get('__name__', None)
elif isinstance(target_module, str):
target_module = importlib.import_module(target_module)
target_namespace = target_module.__dict__
elif inspect.ismodule(target_module):
target_namespace = target_module.__dict__
else:
raise HyTypeError('`target_module` is not a recognized type: {}'.format(
type(target_module)))
# Let's do a quick check to make sure the source module isn't actually
# the module being compiled (e.g. when `runpy` executes a module's code
# in `__main__`).
# We use the module's underlying filename for this (when they exist), since
# it's the most "fixed" attribute.
if _same_modules(source_module, target_module):
return False
if not inspect.ismodule(source_module):
try:
source_module = importlib.import_module(source_module)
except ImportError as e:
reraise(HyRequireError, HyRequireError(e.args[0]), None)
source_macros = source_module.__dict__.setdefault('__macros__', {})
source_tags = source_module.__dict__.setdefault('__tags__', {})
if len(source_module.__macros__) + len(source_module.__tags__) == 0:
if assignments != "ALL":
raise HyRequireError('The module {} has no macros or tags'.format(
source_module))
else:
return False
target_macros = target_namespace.setdefault('__macros__', {})
target_tags = target_namespace.setdefault('__tags__', {})
seen_names = set()
if prefix:
prefix += "."
if assignments != "ALL":
assignments = {mangle(str_type(k)): v for k, v in assignments}
for d in _hy_macros, _hy_tag:
for name, macro in d[source_module].items():
seen_names.add(name)
if assignments == "ALL":
d[target_module][mangle(prefix + name)] = macro
elif name in assignments:
d[target_module][mangle(prefix + assignments[name])] = macro
if assignments == "ALL":
name_assigns = [(k, k) for k in
tuple(source_macros.keys()) + tuple(source_tags.keys())]
else:
name_assigns = assignments
if assignments != "ALL":
unseen = frozenset(assignments.keys()).difference(seen_names)
if unseen:
raise ImportError("cannot require names: " + repr(list(unseen)))
for name, alias in name_assigns:
_name = mangle(name)
alias = mangle(prefix + alias)
if _name in source_module.__macros__:
target_macros[alias] = source_macros[_name]
elif _name in source_module.__tags__:
target_tags[alias] = source_tags[_name]
else:
raise HyRequireError('Could not require name {} from {}'.format(
_name, source_module))
return True
def load_macros(module_name):
def load_macros(module):
"""Load the hy builtin macros for module `module_name`.
Modules from `hy.core` can only use the macros from CORE_MACROS.
Other modules get the macros from CORE_MACROS and EXTRA_MACROS.
"""
builtin_macros = CORE_MACROS
def _import(module, module_name=module_name):
"__import__ a module, avoiding recursions"
if module != module_name:
__import__(module)
if not module.__name__.startswith("hy.core"):
builtin_macros += EXTRA_MACROS
for module in CORE_MACROS:
_import(module)
module_macros = module.__dict__.setdefault('__macros__', {})
module_tags = module.__dict__.setdefault('__tags__', {})
if module_name.startswith("hy.core"):
return
for builtin_mod_name in builtin_macros:
builtin_mod = importlib.import_module(builtin_mod_name)
for module in EXTRA_MACROS:
_import(module)
# Make sure we don't overwrite macros in the module.
if hasattr(builtin_mod, '__macros__'):
module_macros.update({k: v
for k, v in builtin_mod.__macros__.items()
if k not in module_macros})
if hasattr(builtin_mod, '__tags__'):
module_tags.update({k: v
for k, v in builtin_mod.__tags__.items()
if k not in module_tags})
def make_empty_fn_copy(fn):
@contextmanager
def macro_exceptions(module, macro_tree, compiler=None):
try:
# This might fail if fn has parameters with funny names, like o!n. In
# such a case, we return a generic function that ensures the program
# can continue running. Unfortunately, the error message that might get
# raised later on while expanding a macro might not make sense at all.
yield
except HyLanguageError as e:
# These are user-level Hy errors occurring in the macro.
# We want to pass them up to the user.
reraise(type(e), e, sys.exc_info()[2])
except Exception as e:
formatted_args = hy.inspect.format_args(fn)
fn_str = 'lambda {}: None'.format(
formatted_args.lstrip('(').rstrip(')'))
empty_fn = eval(fn_str)
if compiler:
filename = compiler.filename
source = compiler.source
else:
filename = None
source = None
except Exception:
exc_msg = ' '.join(traceback.format_exception_only(
sys.exc_info()[0], sys.exc_info()[1]))
def empty_fn(*args, **kwargs):
None
msg = "expanding macro {}\n ".format(str(macro_tree[0]))
msg += exc_msg
return empty_fn
reraise(HyMacroExpansionError,
HyMacroExpansionError(
msg, macro_tree, filename, source),
sys.exc_info()[2])
def macroexpand(tree, compiler, once=False):
"""Expand the toplevel macros for the `tree`.
def macroexpand(tree, module, compiler=None, once=False):
"""Expand the toplevel macros for the given Hy AST tree.
Load the macros from the given `module_name`, then expand the (top-level)
macros in `tree` until we no longer can.
Load the macros from the given `module`, then expand the (top-level) macros
in `tree` until we no longer can.
`HyExpression` resulting from macro expansions are assigned the module in
which the macro function is defined (determined using `inspect.getmodule`).
If the resulting `HyExpression` is itself macro expanded, then the
namespace of the assigned module is checked first for a macro corresponding
to the expression's head/car symbol. If the head/car symbol of such a
`HyExpression` is not found among the macros of its assigned module's
namespace, the outer-most namespace--e.g. the one given by the `module`
parameter--is used as a fallback.
Parameters
----------
tree: HyObject or list
Hy AST tree.
module: str or types.ModuleType
Module used to determine the local namespace for macros.
compiler: HyASTCompiler, optional
The compiler object passed to expanded macros.
once: boolean, optional
Only expand the first macro in `tree`.
Returns
------
out: HyObject
Returns a mutated tree with macros expanded.
"""
load_macros(compiler.module_name)
if not inspect.ismodule(module):
module = importlib.import_module(module)
assert not compiler or compiler.module == module
while True:
if not isinstance(tree, HyExpression) or tree == []:
@ -174,32 +318,31 @@ def macroexpand(tree, compiler, once=False):
break
fn = mangle(fn)
m = _hy_macros[compiler.module_name].get(fn) or _hy_macros[None].get(fn)
expr_modules = (([] if not hasattr(tree, 'module') else [tree.module])
+ [module])
# Choose the first namespace with the macro.
m = next((mod.__macros__[fn]
for mod in expr_modules
if fn in mod.__macros__),
None)
if not m:
break
opts = {}
if m._hy_macro_pass_compiler:
if compiler is None:
from hy.compiler import HyASTCompiler
compiler = HyASTCompiler(module)
opts['compiler'] = compiler
try:
m_copy = make_empty_fn_copy(m)
m_copy(compiler.module_name, *tree[1:], **opts)
except TypeError as e:
msg = "expanding `" + str(tree[0]) + "': "
msg += str(e).replace("<lambda>()", "", 1).strip()
raise HyMacroExpansionError(tree, msg)
with macro_exceptions(module, tree, compiler):
obj = m(module.__name__, *tree[1:], **opts)
try:
obj = m(compiler.module_name, *tree[1:], **opts)
except HyTypeError as e:
if e.expression is None:
e.expression = tree
raise
except Exception as e:
msg = "expanding `" + str(tree[0]) + "': " + repr(e)
raise HyMacroExpansionError(tree, msg)
tree = replace_hy_obj(obj, tree)
if isinstance(obj, HyExpression):
obj.module = inspect.getmodule(m)
tree = replace_hy_obj(obj, tree)
if once:
break
@ -207,25 +350,56 @@ def macroexpand(tree, compiler, once=False):
tree = wrap_value(tree)
return tree
def macroexpand_1(tree, compiler):
def macroexpand_1(tree, module, compiler=None):
"""Expand the toplevel macro from `tree` once, in the context of
`compiler`."""
return macroexpand(tree, compiler, once=True)
return macroexpand(tree, module, compiler, once=True)
def tag_macroexpand(tag, tree, compiler):
"""Expand the tag macro "tag" with argument `tree`."""
load_macros(compiler.module_name)
def tag_macroexpand(tag, tree, module):
"""Expand the tag macro `tag` with argument `tree`."""
if not inspect.ismodule(module):
module = importlib.import_module(module)
expr_modules = (([] if not hasattr(tree, 'module') else [tree.module])
+ [module])
# Choose the first namespace with the macro.
tag_macro = next((mod.__tags__[tag]
for mod in expr_modules
if tag in mod.__tags__),
None)
tag_macro = _hy_tag[compiler.module_name].get(tag)
if tag_macro is None:
try:
tag_macro = _hy_tag[None][tag]
except KeyError:
raise HyTypeError(
tag,
"`{0}' is not a defined tag macro.".format(tag)
)
raise HyTypeError("`{0}' is not a defined tag macro.".format(tag),
None, tag, None)
expr = tag_macro(tree)
if isinstance(expr, HyExpression):
expr.module = inspect.getmodule(tag_macro)
return replace_hy_obj(expr, tree)
def rename_function(func, new_name):
"""Creates a copy of a function and [re]sets the name at the code-object
level.
"""
c = func.__code__
new_code = type(c)(*[getattr(c, 'co_{}'.format(a))
if a != 'name' else str(new_name)
for a in code_obj_args])
_fn = type(func)(new_code, func.__globals__, str(new_name),
func.__defaults__, func.__closure__)
_fn.__dict__.update(func.__dict__)
return _fn
code_obj_args = ['argcount', 'posonlyargcount', 'kwonlyargcount', 'nlocals', 'stacksize',
'flags', 'code', 'consts', 'names', 'varnames', 'filename', 'name',
'firstlineno', 'lnotab', 'freevars', 'cellvars']
if not PY38:
code_obj_args.remove("posonlyargcount")

View File

@ -1,4 +1,4 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
@ -9,11 +9,13 @@ from funcparserlib.parser import (
some, skip, many, finished, a, Parser, NoParseError, State)
from functools import reduce
from itertools import repeat
from collections import namedtuple
from operator import add
from math import isinf
FORM = some(lambda _: True)
SYM = some(lambda x: isinstance(x, HySymbol))
KEYWORD = some(lambda x: isinstance(x, HyKeyword))
STR = some(lambda x: isinstance(x, HyString))
def sym(wanted):
@ -56,6 +58,14 @@ def notpexpr(*disallowed_heads):
isinstance(x[0], HySymbol) and
x[0] in disallowed_heads))
def unpack(kind):
"Parse an unpacking form, returning it unchanged."
return some(lambda x:
isinstance(x, HyExpression)
and len(x) > 0
and isinstance(x[0], HySymbol)
and x[0] == "unpack-" + kind)
def times(lo, hi, parser):
"""Parse `parser` several times (`lo` to `hi`) in a row. `hi` can be
float('inf'). The result is a list no matter the number of instances."""
@ -74,3 +84,11 @@ def times(lo, hi, parser):
end = e.state.max
return result, State(s.pos, end)
return f
Tag = namedtuple('Tag', ['tag', 'value'])
def tag(tag_name, parser):
"""Matches the given parser and produces a named tuple `(Tag tag value)`
with `tag` set to the given tag name and `value` set to the parser's
value."""
return parser >> (lambda x: Tag(tag_name, x))

View File

@ -1,16 +1,17 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
from __future__ import unicode_literals
from contextlib import contextmanager
from math import isnan, isinf
from hy._compat import PY3, str_type, bytes_type, long_type, string_types
from hy import _initialize_env_var
from hy.errors import HyWrapperError
from fractions import Fraction
from clint.textui import colored
from colorama import Fore
PRETTY = True
COLORED = _initialize_env_var('HY_COLORED_AST_OBJECTS', False)
@contextmanager
@ -27,16 +28,34 @@ def pretty(pretty=True):
PRETTY = old
class _ColoredModel:
"""
Mixin that provides a helper function for models that have color.
"""
def _colored(self, text):
if COLORED:
return self.color + text + Fore.RESET
else:
return text
class HyObject(object):
"""
Generic Hy Object model. This is helpful to inject things into all the
Hy lexing Objects at once.
The position properties (`start_line`, `end_line`, `start_column`,
`end_column`) are each 1-based and inclusive. For example, a symbol
`abc` starting at the first column would have `start_column` 1 and
`end_column` 3.
"""
__properties__ = ["module", "start_line", "end_line", "start_column",
"end_column"]
def replace(self, other, recursive=False):
if isinstance(other, HyObject):
for attr in ["start_line", "end_line",
"start_column", "end_column"]:
for attr in self.__properties__:
if not hasattr(self, attr) and hasattr(other, attr):
setattr(self, attr, getattr(other, attr))
else:
@ -62,7 +81,7 @@ def wrap_value(x):
new = _wrappers.get(type(x), lambda y: y)(x)
if not isinstance(new, HyObject):
raise TypeError("Don't know how to wrap {!r}: {!r}".format(type(x), x))
raise HyWrapperError("Don't know how to wrap {!r}: {!r}".format(type(x), x))
if isinstance(x, HyObject):
new = new.replace(x, recursive=False)
if not hasattr(new, "start_column"):
@ -80,31 +99,32 @@ def repr_indent(obj):
return repr(obj).replace("\n", "\n ")
class HyString(HyObject, str_type):
class HyString(HyObject, str):
"""
Generic Hy String object. Helpful to store string literals from Hy
scripts. It's either a ``str`` or a ``unicode``, depending on the
Python version.
"""
def __new__(cls, s=None, brackets=None):
def __new__(cls, s=None, is_format=False, brackets=None):
value = super(HyString, cls).__new__(cls, s)
value.is_format = bool(is_format)
value.brackets = brackets
return value
_wrappers[str_type] = HyString
_wrappers[str] = HyString
class HyBytes(HyObject, bytes_type):
class HyBytes(HyObject, bytes):
"""
Generic Hy Bytes object. It's either a ``bytes`` or a ``str``, depending
on the Python version.
"""
pass
_wrappers[bytes_type] = HyBytes
_wrappers[bytes] = HyBytes
class HySymbol(HyObject, str_type):
class HySymbol(HyObject, str):
"""
Hy Symbol. Basically a string.
"""
@ -146,47 +166,54 @@ class HyKeyword(HyObject):
def __bool__(self):
return bool(self.name)
_sentinel = object()
def __call__(self, data, default=_sentinel):
try:
return data[self]
except KeyError:
if default is HyKeyword._sentinel:
raise
return default
def strip_digit_separators(number):
# Don't strip a _ or , if it's the first character, as _42 and
# ,42 aren't valid numbers
return (number[0] + number[1:].replace("_", "").replace(",", "")
if isinstance(number, string_types) and len(number) > 1
if isinstance(number, str) and len(number) > 1
else number)
class HyInteger(HyObject, long_type):
class HyInteger(HyObject, int):
"""
Internal representation of a Hy Integer. May raise a ValueError as if
int(foo) was called, given HyInteger(foo). On python 2.x long will
be used instead
int(foo) was called, given HyInteger(foo).
"""
def __new__(cls, number, *args, **kwargs):
if isinstance(number, string_types):
if isinstance(number, str):
number = strip_digit_separators(number)
bases = {"0x": 16, "0o": 8, "0b": 2}
for leader, base in bases.items():
if number.startswith(leader):
# We've got a string, known leader, set base.
number = long_type(number, base=base)
number = int(number, base=base)
break
else:
# We've got a string, no known leader; base 10.
number = long_type(number, base=10)
number = int(number, base=10)
else:
# We've got a non-string; convert straight.
number = long_type(number)
number = int(number)
return super(HyInteger, cls).__new__(cls, number)
_wrappers[int] = HyInteger
if not PY3: # do not add long on python3
_wrappers[long_type] = HyInteger
def check_inf_nan_cap(arg, value):
if isinstance(arg, string_types):
if isinstance(arg, str):
if isinf(value) and "i" in arg.lower() and "Inf" not in arg:
raise ValueError('Inf must be capitalized as "Inf"')
if isnan(value) and "NaN" not in arg:
@ -214,7 +241,7 @@ class HyComplex(HyObject, complex):
"""
def __new__(cls, real, imag=0, *args, **kwargs):
if isinstance(real, string_types):
if isinstance(real, str):
value = super(HyComplex, cls).__new__(
cls, strip_digit_separators(real)
)
@ -228,7 +255,7 @@ class HyComplex(HyObject, complex):
_wrappers[complex] = HyComplex
class HySequence(HyObject, list):
class HySequence(HyObject, tuple, _ColoredModel):
"""
An abstract type for sequence-like models to inherit from.
"""
@ -241,7 +268,8 @@ class HySequence(HyObject, list):
return self
def __add__(self, other):
return self.__class__(super(HySequence, self).__add__(other))
return self.__class__(super(HySequence, self).__add__(
tuple(other) if isinstance(other, list) else other))
def __getslice__(self, start, end):
return self.__class__(super(HySequence, self).__getslice__(start, end))
@ -261,19 +289,24 @@ class HySequence(HyObject, list):
def __str__(self):
with pretty():
c = self.color
if self:
return ("{}{}\n {}{}").format(
c(self.__class__.__name__),
c("(["),
(c(",") + "\n ").join([repr_indent(e) for e in self]),
c("])"))
return self._colored("{}{}\n {}{}".format(
self._colored(self.__class__.__name__),
self._colored("(["),
self._colored(",\n ").join(map(repr_indent, self)),
self._colored("])"),
))
return self._colored("{}([\n {}])".format(
self.__class__.__name__,
','.join(repr_indent(e) for e in self),
))
else:
return '' + c(self.__class__.__name__ + "()")
return self._colored(self.__class__.__name__ + "()")
class HyList(HySequence):
color = staticmethod(colored.cyan)
color = Fore.CYAN
def recwrap(f):
return lambda l: f(wrap_value(x) for x in l)
@ -283,14 +316,14 @@ _wrappers[list] = recwrap(HyList)
_wrappers[tuple] = recwrap(HyList)
class HyDict(HySequence):
class HyDict(HySequence, _ColoredModel):
"""
HyDict (just a representation of a dict)
"""
color = Fore.GREEN
def __str__(self):
with pretty():
g = colored.green
if self:
pairs = []
for k, v in zip(self[::2],self[1::2]):
@ -298,20 +331,22 @@ class HyDict(HySequence):
pairs.append(
("{0}{c}\n {1}\n "
if '\n' in k+v
else "{0}{c} {1}").format(k, v, c=g(',')))
else "{0}{c} {1}").format(k, v, c=self._colored(',')))
if len(self) % 2 == 1:
pairs.append("{} {}\n".format(
repr_indent(self[-1]), g("# odd")))
repr_indent(self[-1]), self._colored("# odd")))
return "{}\n {}{}".format(
g("HyDict(["), ("{c}\n ".format(c=g(',')).join(pairs)), g("])"))
self._colored("HyDict(["),
"{c}\n ".format(c=self._colored(',')).join(pairs),
self._colored("])"))
else:
return '' + g("HyDict()")
return self._colored("HyDict()")
def keys(self):
return self[0::2]
return list(self[0::2])
def values(self):
return self[1::2]
return list(self[1::2])
def items(self):
return list(zip(self.keys(), self.values()))
@ -324,7 +359,7 @@ class HyExpression(HySequence):
"""
Hy S-Expression. Basically just a list.
"""
color = staticmethod(colored.yellow)
color = Fore.YELLOW
_wrappers[HyExpression] = recwrap(HyExpression)
_wrappers[Fraction] = lambda e: HyExpression(
@ -335,7 +370,7 @@ class HySet(HySequence):
"""
Hy set (just a representation of a set)
"""
color = staticmethod(colored.red)
color = Fore.RED
_wrappers[HySet] = recwrap(HySet)
_wrappers[set] = recwrap(HySet)

7
issue_template.md Normal file
View File

@ -0,0 +1,7 @@
Hy's issue list is for bugs, complaints, and feature requests.
For help with Hy, ask on our IRC channel (may take up to a day), Stack Overflow with the `[hy]` tag, or on our mailing list.
If you're reporting a bug, make sure you can reproduce it with the very latest, bleeding-edge version of Hy from the `master` branch on GitHub. Bugs in stable versions of Hy are fixed on `master` before the fix makes it into a new stable release.
You can delete this text after reading it.

View File

@ -1,41 +0,0 @@
;; Copyright 2018 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
;; You need to install the requests package first
(import os.path)
(import requests)
(setv *api-url* "https://api.github.com/{}")
(setv *rst-format* "* `{} <{}>`_")
(setv *missing-names* {"khinsen" "Konrad Hinsen"})
;; We have three concealed members on the hylang organization
;; and GitHub only shows public members if the requester is not
;; an owner of the organization.
(setv *concealed-members* [(, "aldeka" "Karen Rustad")
(, "tuturto" "Tuukka Turto")
(, "cndreisbach" "Clinton N. Dreisbach")])
(defn get-dev-name [login]
(setv name (get (.json (requests.get (.format *api-url* (+ "users/" login)))) "name"))
(if-not name
(.get *missing-names* login)
name))
(setv coredevs (requests.get (.format *api-url* "orgs/hylang/members")))
(setv result (set))
(for [dev (.json coredevs)]
(result.add (.format *rst-format* (get-dev-name (get dev "login"))
(get dev "html_url"))))
(for [(, login name) *concealed-members*]
(result.add (.format *rst-format* name (+ "https://github.com/" login))))
(setv filename (os.path.abspath (os.path.join os.path.pardir
"docs" "coreteam.rst")))
(with [fobj (open filename "w+")]
(fobj.write (+ (.join "\n" result) "\n")))

View File

@ -17,9 +17,9 @@ exclude_lines =
ignore_errors = True
[tool:pytest]
# Be sure to include Hy test functions whose names end with "?",
# which will be mangled to begin with "is_".
python_functions=test_* is_test_*
# Be sure to include Hy test functions with mangled names.
python_functions=test_* is_test_* hyx_test_* hyx_is_test_*
filterwarnings =
once::DeprecationWarning
once::PendingDeprecationWarning
ignore::SyntaxWarning

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
@ -7,6 +7,7 @@ import sys, os
from setuptools import find_packages, setup
from setuptools.command.install import install
import fastentrypoints # Monkey-patches setuptools.
from get_version import __version__
@ -30,28 +31,27 @@ class Install(install):
"." + filename[:-len(".hy")])
install.run(self)
install_requires = ['rply>=0.7.6', 'astor', 'funcparserlib>=0.3.6', 'clint>=0.4']
install_requires = [
'rply>=0.7.7',
'astor>=0.8',
'funcparserlib>=0.3.6',
'colorama']
if os.name == 'nt':
install_requires.append('pyreadline>=2.1')
ver = sys.version_info[0]
setup(
name=PKG,
version=__version__,
install_requires=install_requires,
dependency_links=[
'git+https://github.com/berkerpeksag/astor.git#egg=astor-0.7.0'
],
cmdclass=dict(install=Install),
entry_points={
'console_scripts': [
'hy = hy.cmdline:hy_main',
'hy%d = hy.cmdline:hy_main' % ver,
'hy3 = hy.cmdline:hy_main',
'hyc = hy.cmdline:hyc_main',
'hyc%d = hy.cmdline:hyc_main' % ver,
'hyc3 = hy.cmdline:hyc_main',
'hy2py = hy.cmdline:hy2py_main',
'hy2py%d = hy.cmdline:hy2py_main' % ver,
'hy2py3 = hy.cmdline:hy2py_main',
]
},
packages=find_packages(exclude=['tests*']),
@ -78,13 +78,11 @@ setup(
"Operating System :: OS Independent",
"Programming Language :: Lisp",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Code Generators",
"Topic :: Software Development :: Compilers",
"Topic :: Software Development :: Libraries",

View File

@ -1,17 +1,16 @@
# -*- encoding: utf-8 -*-
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
from __future__ import unicode_literals
from hy import HyString
from hy.models import HyObject
from hy.compiler import hy_compile
from hy.importer import hy_eval, import_buffer_to_hst
from hy.errors import HyCompileError, HyTypeError
from hy.lex.exceptions import LexException
from hy._compat import PY3
from hy.compiler import hy_compile, hy_eval
from hy.errors import HyCompileError, HyLanguageError, HyError
from hy.lex import hy_parse
from hy.lex.exceptions import LexException, PrematureEndOfInput
from hy._compat import PY36
import ast
import pytest
@ -27,29 +26,24 @@ def _ast_spotcheck(arg, root, secondary):
def can_compile(expr):
return hy_compile(import_buffer_to_hst(expr), "__main__")
return hy_compile(hy_parse(expr), __name__)
def can_eval(expr):
return hy_eval(import_buffer_to_hst(expr))
return hy_eval(hy_parse(expr))
def cant_compile(expr):
try:
hy_compile(import_buffer_to_hst(expr), "__main__")
assert False
except HyTypeError as e:
with pytest.raises(HyError) as excinfo:
hy_compile(hy_parse(expr), __name__)
if issubclass(excinfo.type, HyLanguageError):
assert excinfo.value.msg
return excinfo.value
elif issubclass(excinfo.type, HyCompileError):
# Anything that can't be compiled should raise a user friendly
# error, otherwise it's a compiler bug.
assert isinstance(e.expression, HyObject)
assert e.message
return e
except HyCompileError as e:
# Anything that can't be compiled should raise a user friendly
# error, otherwise it's a compiler bug.
assert isinstance(e.exception, HyTypeError)
assert e.traceback
return e
return excinfo.value
def s(x):
@ -60,11 +54,26 @@ def test_ast_bad_type():
"Make sure AST breakage can happen"
class C:
pass
try:
hy_compile(C(), "__main__")
assert True is False
except TypeError:
pass
with pytest.raises(TypeError):
hy_compile(C(), __name__, filename='<string>', source='')
def test_empty_expr():
"Empty expressions should be illegal at the top level."
cant_compile("(print ())")
can_compile("(print '())")
def test_dot_unpacking():
can_compile("(.meth obj #* args az)")
cant_compile("(.meth #* args az)")
cant_compile("(. foo #* bar baz)")
can_compile("(.meth obj #** args az)")
can_compile("(.meth #** args obj)")
cant_compile("(. foo #** bar baz)")
def test_ast_bad_if():
@ -112,9 +121,8 @@ def test_ast_good_raise():
can_compile("(raise e)")
if PY3:
def test_ast_raise_from():
can_compile("(raise Exception :from NameError)")
def test_ast_raise_from():
can_compile("(raise Exception :from NameError)")
def test_ast_bad_raise():
@ -196,16 +204,16 @@ def test_ast_bad_global():
cant_compile("(global (foo))")
if PY3:
def test_ast_good_nonlocal():
"Make sure AST can compile valid nonlocal"
can_compile("(nonlocal a)")
can_compile("(nonlocal foo bar)")
def test_ast_good_nonlocal():
"Make sure AST can compile valid nonlocal"
can_compile("(nonlocal a)")
can_compile("(nonlocal foo bar)")
def test_ast_bad_nonlocal():
"Make sure AST can't compile invalid nonlocal"
cant_compile("(nonlocal)")
cant_compile("(nonlocal (foo))")
def test_ast_bad_nonlocal():
"Make sure AST can't compile invalid nonlocal"
cant_compile("(nonlocal)")
cant_compile("(nonlocal (foo))")
def test_ast_good_defclass():
@ -217,7 +225,6 @@ def test_ast_good_defclass():
can_compile("(defclass a [] None (print \"foo\"))")
@pytest.mark.skipif(not PY3, reason="Python 3 supports class keywords")
def test_ast_good_defclass_with_metaclass():
"Make sure AST can compile valid defclass with keywords"
can_compile("(defclass a [:metaclass b])")
@ -283,19 +290,11 @@ def test_ast_require():
cant_compile("(require [tests.resources.tlib [* *]])")
def test_ast_no_pointless_imports():
def contains_import_from(code):
return any([isinstance(node, ast.ImportFrom)
for node in can_compile(code).body])
# `reduce` is a builtin in Python 2, but not Python 3.
# The version of `map` that returns an iterator is a builtin in
# Python 3, but not Python 2.
if PY3:
assert contains_import_from("reduce")
assert not contains_import_from("map")
else:
assert not contains_import_from("reduce")
assert contains_import_from("map")
def test_ast_import_require_dotted():
"""As in Python, it should be a compile-time error to attempt to
import a dotted name."""
cant_compile("(import [spam [foo.bar]])")
cant_compile("(require [spam [foo.bar]])")
def test_ast_good_get():
@ -438,36 +437,27 @@ def test_lambda_list_keywords_kwargs():
def test_lambda_list_keywords_kwonly():
"""Ensure we can compile functions with &kwonly if we're on Python
3, or fail with an informative message on Python 2."""
kwonly_demo = "(fn [&kwonly a [b 2]] (print 1) (print a b))"
if PY3:
code = can_compile(kwonly_demo)
for i, kwonlyarg_name in enumerate(('a', 'b')):
assert kwonlyarg_name == code.body[0].args.kwonlyargs[i].arg
assert code.body[0].args.kw_defaults[0] is None
assert code.body[0].args.kw_defaults[1].n == 2
else:
exception = cant_compile(kwonly_demo)
assert isinstance(exception, HyTypeError)
message, = exception.args
assert message == "&kwonly parameters require Python 3"
code = can_compile(kwonly_demo)
for i, kwonlyarg_name in enumerate(('a', 'b')):
assert kwonlyarg_name == code.body[0].args.kwonlyargs[i].arg
assert code.body[0].args.kw_defaults[0] is None
assert code.body[0].args.kw_defaults[1].n == 2
def test_lambda_list_keywords_mixed():
""" Ensure we can mix them up."""
can_compile("(fn [x &rest xs &kwargs kw] (list x xs kw))")
cant_compile("(fn [x &rest xs &fasfkey {bar \"baz\"}])")
if PY3:
can_compile("(fn [x &rest xs &kwonly kwoxs &kwargs kwxs]"
" (list x xs kwxs kwoxs))")
can_compile("(fn [x &rest xs &kwonly kwoxs &kwargs kwxs]"
" (list x xs kwxs kwoxs))")
def test_missing_keyword_argument_value():
"""Ensure the compiler chokes on missing keyword argument values."""
with pytest.raises(HyTypeError) as excinfo:
with pytest.raises(HyLanguageError) as excinfo:
can_compile("((fn [x] x) :x)")
assert excinfo.value.message == "Keyword argument :x needs a value."
assert excinfo.value.msg == "Keyword argument :x needs a value."
def test_ast_unicode_strings():
@ -476,7 +466,7 @@ def test_ast_unicode_strings():
def _compile_string(s):
hy_s = HyString(s)
code = hy_compile([hy_s], "__main__")
code = hy_compile([hy_s], __name__, filename='<string>', source=s)
# We put hy_s in a list so it isn't interpreted as a docstring.
# code == ast.Module(body=[ast.Expr(value=ast.List(elts=[ast.Str(s=xxx)]))])
@ -488,11 +478,23 @@ def test_ast_unicode_strings():
def test_ast_unicode_vs_bytes():
assert s('"hello"') == u"hello"
assert type(s('"hello"')) is (str if PY3 else unicode) # noqa
assert s('b"hello"') == (eval('b"hello"') if PY3 else "hello")
assert type(s('b"hello"')) is (bytes if PY3 else str)
assert s('b"\\xa0"') == (bytes([160]) if PY3 else chr(160))
assert s('"hello"') == "hello"
assert type(s('"hello"')) is str
assert s('b"hello"') == b"hello"
assert type(s('b"hello"')) is bytes
assert s('b"\\xa0"') == bytes([160])
@pytest.mark.skipif(not PY36, reason='f-strings require Python 3.6+')
def test_format_string():
assert can_compile('f"hello world"')
assert can_compile('f"hello {(+ 1 1)} world"')
assert can_compile('f"hello world {(+ 1 1)}"')
assert cant_compile('f"hello {(+ 1 1) world"')
assert cant_compile('f"hello (+ 1 1)} world"')
assert cant_compile('f"hello {(+ 1 1} world"')
assert can_compile(r'f"hello {\"n\"} world"')
assert can_compile(r'f"hello {\"\\n\"} world"')
def test_ast_bracket_string():
@ -500,7 +502,7 @@ def test_ast_bracket_string():
assert s(r'#[my delim[fizzle]my delim]') == 'fizzle'
assert s(r'#[[]]') == ''
assert s(r'#[my delim[]my delim]') == ''
assert type(s('#[X[hello]X]')) is (str if PY3 else unicode) # noqa
assert type(s('#[X[hello]X]')) is str
assert s(r'#[X[raw\nstring]X]') == 'raw\\nstring'
assert s(r'#[foozle[aa foozli bb ]foozle]') == 'aa foozli bb '
assert s(r'#[([unbalanced](]') == 'unbalanced'
@ -517,23 +519,21 @@ Only one leading newline should be removed.
def test_compile_error():
"""Ensure we get compile error in tricky cases"""
with pytest.raises(HyTypeError) as excinfo:
with pytest.raises(HyLanguageError) as excinfo:
can_compile("(fn [] (in [1 2 3]))")
def test_for_compile_error():
"""Ensure we get compile error in tricky 'for' cases"""
with pytest.raises(LexException) as excinfo:
with pytest.raises(PrematureEndOfInput) as excinfo:
can_compile("(fn [] (for)")
assert excinfo.value.message == "Premature end of input"
assert excinfo.value.msg == "Premature end of input"
with pytest.raises(LexException) as excinfo:
can_compile("(fn [] (for)))")
assert excinfo.value.message == "Ran into a RPAREN where it wasn't expected."
assert excinfo.value.msg == "Ran into a RPAREN where it wasn't expected."
with pytest.raises(HyTypeError) as excinfo:
can_compile("(fn [] (for [x] x))")
assert excinfo.value.message == "`for' requires an even number of args."
cant_compile("(fn [] (for [x] x))")
def test_attribute_access():
@ -556,14 +556,6 @@ def test_attribute_empty():
cant_compile('[2].foo')
def test_invalid_list_comprehension():
"""Ensure that invalid list comprehensions do not break the compiler"""
cant_compile("(genexpr x [])")
cant_compile("(genexpr [x [1 2 3 4]] x)")
cant_compile("(list-comp None [])")
cant_compile("(list-comp [x [1 2 3]] x)")
def test_bad_setv():
"""Ensure setv handles error cases"""
cant_compile("(setv (a b) [1 2])")
@ -591,13 +583,13 @@ def test_setv_builtins():
def test_top_level_unquote():
with pytest.raises(HyTypeError) as excinfo:
with pytest.raises(HyLanguageError) as excinfo:
can_compile("(unquote)")
assert excinfo.value.message == "The special form 'unquote' is not allowed here"
assert excinfo.value.msg == "The special form 'unquote' is not allowed here"
with pytest.raises(HyTypeError) as excinfo:
with pytest.raises(HyLanguageError) as excinfo:
can_compile("(unquote-splice)")
assert excinfo.value.message == "The special form 'unquote-splice' is not allowed here"
assert excinfo.value.msg == "The special form 'unquote-splice' is not allowed here"
def test_lots_of_comment_lines():
@ -605,30 +597,6 @@ def test_lots_of_comment_lines():
can_compile(1000 * ";\n")
def test_exec_star():
code = can_compile('(exec* "print(5)")').body[0]
assert type(code) == (ast.Expr if PY3 else ast.Exec)
if not PY3:
assert code.body.s == "print(5)"
assert code.globals is None
assert code.locals is None
code = can_compile('(exec* "print(a)" {"a" 3})').body[0]
assert type(code) == (ast.Expr if PY3 else ast.Exec)
if not PY3:
assert code.body.s == "print(a)"
assert code.globals.keys[0].s == "a"
assert code.locals is None
code = can_compile('(exec* "print(a + b)" {"a" "x"} {"b" "y"})').body[0]
assert type(code) == (ast.Expr if PY3 else ast.Exec)
if not PY3:
assert code.body.s == "print(a + b)"
assert code.globals.keys[0].s == "a"
assert code.locals.keys[0].s == "b"
def test_compiler_macro_tag_try():
"""Check that try forms within defmacro/deftag are compiled correctly"""
# https://github.com/hylang/hy/issues/1350
@ -636,13 +604,11 @@ def test_compiler_macro_tag_try():
can_compile("(deftag foo [] (try None (except [] None)) `())")
@pytest.mark.skipif(not PY3, reason="Python 3 required")
def test_ast_good_yield_from():
"Make sure AST can compile valid yield-from"
can_compile("(yield-from [1 2])")
@pytest.mark.skipif(not PY3, reason="Python 3 required")
def test_ast_bad_yield_from():
"Make sure AST can't compile invalid yield-from"
cant_compile("(yield-from)")
@ -651,3 +617,32 @@ def test_ast_bad_yield_from():
def test_eval_generator_with_return():
"""Ensure generators with a return statement works."""
can_eval("(fn [] (yield 1) (yield 2) (return))")
def test_futures_imports():
"""Make sure __future__ imports go first, especially when builtins are
automatically added (e.g. via use of a builtin name like `name`)."""
hy_ast = can_compile((
'(import [__future__ [print_function]])\n'
'(import sys)\n'
'(setv name [1 2])'
'(print (first name))'))
assert hy_ast.body[0].module == '__future__'
assert hy_ast.body[1].module == 'hy.core.language'
hy_ast = can_compile((
'(import sys)\n'
'(import [__future__ [print_function]])\n'
'(setv name [1 2])'
'(print (first name))'))
assert hy_ast.body[0].module == '__future__'
assert hy_ast.body[1].module == 'hy.core.language'
def test_inline_python():
can_compile('(py "1 + 1")')
cant_compile('(py "1 +")')
can_compile('(pys "if 1:\n 2")')
cant_compile('(pys "if 1\n 2")')

View File

@ -1,4 +1,4 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
@ -6,7 +6,7 @@ import ast
from hy import compiler
from hy.models import HyExpression, HyList, HySymbol, HyInteger
from hy._compat import PY3
import types
def make_expression(*args):
@ -26,7 +26,7 @@ def test_compiler_bare_names():
HySymbol("a"),
HySymbol("b"),
HySymbol("c"))
ret = compiler.HyASTCompiler('test').compile(e)
ret = compiler.HyASTCompiler(types.ModuleType('test')).compile(e)
# We expect two statements and a final expr.
@ -55,7 +55,7 @@ def test_compiler_yield_return():
HyExpression([HySymbol("+"),
HyInteger(1),
HyInteger(1)]))
ret = compiler.HyASTCompiler('test').compile_atom(e)
ret = compiler.HyASTCompiler(types.ModuleType('test')).compile_atom(e)
assert len(ret.stmts) == 1
stmt, = ret.stmts
@ -64,12 +64,5 @@ def test_compiler_yield_return():
assert len(body) == 2
assert isinstance(body[0], ast.Expr)
assert isinstance(body[0].value, ast.Yield)
if PY3:
# From 3.3+, the final statement becomes a return value
assert isinstance(body[1], ast.Return)
assert isinstance(body[1].value, ast.BinOp)
else:
# In earlier versions, the expression is not returned
assert isinstance(body[1], ast.Expr)
assert isinstance(body[1].value, ast.BinOp)
assert isinstance(body[1], ast.Return)
assert isinstance(body[1].value, ast.BinOp)

View File

@ -1,36 +1,70 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
import hy
from hy.importer import (import_file_to_module, import_buffer_to_ast,
MetaLoader, get_bytecode_path)
from hy.errors import HyTypeError
import os
import sys
import ast
import tempfile
import runpy
import importlib
from fractions import Fraction
from importlib import reload
import pytest
import hy
from hy.lex import hy_parse
from hy.errors import HyLanguageError
from hy.lex.exceptions import PrematureEndOfInput
from hy.compiler import hy_eval, hy_compile
from hy.importer import HyLoader
def test_basics():
"Make sure the basics of the importer work"
import_file_to_module("basic",
"tests/resources/importer/basic.hy")
assert os.path.isfile('tests/resources/__init__.py')
resources_mod = importlib.import_module('tests.resources')
assert hasattr(resources_mod, 'kwtest')
assert os.path.isfile('tests/resources/bin/__init__.hy')
bin_mod = importlib.import_module('tests.resources.bin')
assert hasattr(bin_mod, '_null_fn_for_import_test')
def test_runpy():
# XXX: `runpy` won't update cached bytecode! Don't know if that's
# intentional or not.
basic_ns = runpy.run_path('tests/resources/importer/basic.hy')
assert 'square' in basic_ns
main_ns = runpy.run_path('tests/resources/bin')
assert main_ns['visited_main'] == 1
del main_ns
main_ns = runpy.run_module('tests.resources.bin')
assert main_ns['visited_main'] == 1
with pytest.raises(IOError):
runpy.run_path('tests/resources/foobarbaz.py')
def test_stringer():
_ast = import_buffer_to_ast("(defn square [x] (* x x))", '')
_ast = hy_compile(hy_parse("(defn square [x] (* x x))"), __name__)
assert type(_ast.body[0]) == ast.FunctionDef
def test_imports():
path = os.getcwd() + "/tests/resources/importer/a.hy"
testLoader = MetaLoader(path)
testLoader = HyLoader("tests.resources.importer.a", path)
def _import_test():
try:
return testLoader.load_module("tests.resources.importer.a")
return testLoader.load_module()
except:
return "Error"
@ -41,39 +75,46 @@ def test_imports():
def test_import_error_reporting():
"Make sure that (import) reports errors correctly."
def _import_error_test():
try:
import_buffer_to_ast("(import \"sys\")", '')
except HyTypeError:
return "Error reported"
assert _import_error_test() == "Error reported"
assert _import_error_test() is not None
with pytest.raises(HyLanguageError):
hy_compile(hy_parse("(import \"sys\")"), __name__)
@pytest.mark.skipif(os.environ.get('PYTHONDONTWRITEBYTECODE'),
def test_import_error_cleanup():
"Failed initial imports should not leave dead modules in `sys.modules`."
with pytest.raises(hy.errors.HyMacroExpansionError):
importlib.import_module('tests.resources.fails')
assert 'tests.resources.fails' not in sys.modules
@pytest.mark.skipif(sys.dont_write_bytecode,
reason="Bytecode generation is suppressed")
def test_import_autocompiles():
"Test that (import) byte-compiles the module."
f = tempfile.NamedTemporaryFile(suffix='.hy', delete=False)
f.write(b'(defn pyctest [s] (+ "X" s "Y"))')
f.close()
with tempfile.NamedTemporaryFile(suffix='.hy', delete=True) as f:
f.write(b'(defn pyctest [s] (+ "X" s "Y"))')
f.flush()
try:
os.remove(get_bytecode_path(f.name))
except (IOError, OSError):
pass
import_file_to_module("mymodule", f.name)
assert os.path.exists(get_bytecode_path(f.name))
pyc_path = importlib.util.cache_from_source(f.name)
os.remove(f.name)
os.remove(get_bytecode_path(f.name))
try:
os.remove(pyc_path)
except (IOError, OSError):
pass
test_loader = HyLoader("mymodule", f.name).load_module()
assert hasattr(test_loader, 'pyctest')
assert os.path.exists(pyc_path)
os.remove(pyc_path)
def test_eval():
def eval_str(s):
return hy.eval(hy.read_str(s))
return hy_eval(hy.read_str(s), filename='<string>', source=s)
assert eval_str('[1 2 3]') == [1, 2, 3]
assert eval_str('{"dog" "bark" "cat" "meow"}') == {
@ -84,5 +125,167 @@ def test_eval():
assert eval_str('(.strip " fooooo ")') == 'fooooo'
assert eval_str(
'(if True "this is if true" "this is if false")') == "this is if true"
assert eval_str('(list-comp (pow num 2) [num (range 100)] (= (% num 2) 1))') == [
assert eval_str('(lfor num (range 100) :if (= (% num 2) 1) (pow num 2))') == [
pow(num, 2) for num in range(100) if num % 2 == 1]
def test_reload():
"""Generate a test module, confirm that it imports properly (and puts the
module in `sys.modules`), then modify the module so that it produces an
error when reloaded. Next, fix the error, reload, and check that the
module is updated and working fine. Rinse, repeat.
This test is adapted from CPython's `test_import.py`.
"""
def unlink(filename):
os.unlink(source)
bytecode = importlib.util.cache_from_source(source)
if os.path.isfile(bytecode):
os.unlink(bytecode)
TESTFN = 'testfn'
source = TESTFN + os.extsep + "hy"
with open(source, "w") as f:
f.write("(setv a 1)")
f.write("(setv b 2)")
sys.path.insert(0, os.curdir)
try:
mod = importlib.import_module(TESTFN)
assert TESTFN in sys.modules
assert mod.a == 1
assert mod.b == 2
# On WinXP, just replacing the .py file wasn't enough to
# convince reload() to reparse it. Maybe the timestamp didn't
# move enough. We force it to get reparsed by removing the
# compiled file too.
unlink(source)
# Now damage the module.
with open(source, "w") as f:
f.write("(setv a 10)")
f.write("(setv b (// 20 0))")
with pytest.raises(ZeroDivisionError):
reload(mod)
# But we still expect the module to be in sys.modules.
mod = sys.modules.get(TESTFN)
assert mod is not None
# We should have replaced a w/ 10, but the old b value should
# stick.
assert mod.a == 10
assert mod.b == 2
# Now fix the issue and reload the module.
unlink(source)
with open(source, "w") as f:
f.write("(setv a 11)")
f.write("(setv b (// 20 1))")
reload(mod)
mod = sys.modules.get(TESTFN)
assert mod is not None
assert mod.a == 11
assert mod.b == 20
# Now cause a syntax error
unlink(source)
with open(source, "w") as f:
# Missing paren...
f.write("(setv a 11")
f.write("(setv b (// 20 1))")
with pytest.raises(PrematureEndOfInput):
reload(mod)
mod = sys.modules.get(TESTFN)
assert mod is not None
assert mod.a == 11
assert mod.b == 20
# Fix it and retry
unlink(source)
with open(source, "w") as f:
f.write("(setv a 12)")
f.write("(setv b (// 10 1))")
reload(mod)
mod = sys.modules.get(TESTFN)
assert mod is not None
assert mod.a == 12
assert mod.b == 10
finally:
del sys.path[0]
if TESTFN in sys.modules:
del sys.modules[TESTFN]
unlink(source)
def test_reload_reexecute(capsys):
"""A module is re-executed when it's reloaded, even if it's
unchanged.
https://github.com/hylang/hy/issues/712"""
import tests.resources.hello_world
assert capsys.readouterr().out == 'hello world\n'
assert capsys.readouterr().out == ''
reload(tests.resources.hello_world)
assert capsys.readouterr().out == 'hello world\n'
def test_circular():
"""Test circular imports by creating a temporary file/module that calls a
function that imports itself."""
sys.path.insert(0, os.path.abspath('tests/resources/importer'))
try:
mod = runpy.run_module('circular')
assert mod['f']() == 1
finally:
sys.path.pop(0)
def test_shadowed_basename():
"""Make sure Hy loads `.hy` files instead of their `.py` counterparts (.e.g
`__init__.py` and `__init__.hy`).
"""
sys.path.insert(0, os.path.realpath('tests/resources/importer'))
try:
assert os.path.isfile('tests/resources/importer/foo/__init__.hy')
assert os.path.isfile('tests/resources/importer/foo/__init__.py')
assert os.path.isfile('tests/resources/importer/foo/some_mod.hy')
assert os.path.isfile('tests/resources/importer/foo/some_mod.py')
foo = importlib.import_module('foo')
assert foo.__file__.endswith('foo/__init__.hy')
assert foo.ext == 'hy'
some_mod = importlib.import_module('foo.some_mod')
assert some_mod.__file__.endswith('foo/some_mod.hy')
assert some_mod.ext == 'hy'
finally:
sys.path.pop(0)
def test_docstring():
"""Make sure a module's docstring is loaded."""
sys.path.insert(0, os.path.realpath('tests/resources/importer'))
try:
mod = importlib.import_module('docstring')
expected_doc = ("This module has a docstring.\n\n"
"It covers multiple lines, too!\n")
assert mod.__doc__ == expected_doc
assert mod.a == 1
finally:
sys.path.pop(0)

View File

@ -1,24 +1,27 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
import os
import imp
import importlib.util
import py_compile
import tempfile
from hy.importer import write_hy_as_pyc, get_bytecode_path
import hy.importer
def test_pyc():
"""Test pyc compilation."""
f = tempfile.NamedTemporaryFile(suffix='.hy', delete=False)
f.write(b'(defn pyctest [s] (+ "X" s "Y"))')
f.close()
with tempfile.NamedTemporaryFile(suffix='.hy') as f:
f.write(b'(defn pyctest [s] (+ "X" s "Y"))')
f.flush()
write_hy_as_pyc(f.name)
os.remove(f.name)
cfile = py_compile.compile(f.name)
assert os.path.exists(cfile)
cfile = get_bytecode_path(f.name)
mod = imp.load_compiled('pyc', cfile)
os.remove(cfile)
try:
mod = hy.importer._import_from_path('pyc', cfile)
finally:
os.remove(cfile)
assert mod.pyctest('Foo') == 'XFooY'
assert mod.pyctest('Foo') == 'XFooY'

View File

@ -1,4 +1,4 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
@ -22,6 +22,7 @@ def tmac(ETname, *tree):
def test_preprocessor_simple():
""" Test basic macro expansion """
obj = macroexpand(tokenize('(test "one" "two")')[0],
__name__,
HyASTCompiler(__name__))
assert obj == HyList(["one", "two"])
assert type(obj) == HyList
@ -30,6 +31,7 @@ def test_preprocessor_simple():
def test_preprocessor_expression():
""" Test that macro expansion doesn't recurse"""
obj = macroexpand(tokenize('(test (test "one" "two"))')[0],
__name__,
HyASTCompiler(__name__))
assert type(obj) == HyList
@ -41,21 +43,20 @@ def test_preprocessor_expression():
obj = HyList([HyString("one"), HyString("two")])
obj = tokenize('(shill ["one" "two"])')[0][1]
assert obj == macroexpand(obj, HyASTCompiler(""))
assert obj == macroexpand(obj, __name__, HyASTCompiler(__name__))
def test_preprocessor_exceptions():
""" Test that macro expansion raises appropriate exceptions"""
with pytest.raises(HyMacroExpansionError) as excinfo:
macroexpand(tokenize('(defn)')[0], HyASTCompiler(__name__))
assert "_hy_anon_fn_" not in excinfo.value.message
assert "TypeError" not in excinfo.value.message
macroexpand(tokenize('(defn)')[0], __name__, HyASTCompiler(__name__))
assert "_hy_anon_" not in excinfo.value.msg
def test_macroexpand_nan():
# https://github.com/hylang/hy/issues/1574
import math
NaN = float('nan')
x = macroexpand(HyFloat(NaN), HyASTCompiler(__name__))
x = macroexpand(HyFloat(NaN), __name__, HyASTCompiler(__name__))
assert type(x) is HyFloat
assert math.isnan(x)

View File

@ -1,4 +1,4 @@
# Copyright 2018 the authors.
# Copyright 2019 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
@ -11,6 +11,7 @@ def test_tag_macro_error():
"""Check if we get correct error with wrong dispatch character"""
try:
macroexpand(tokenize("(dispatch_tag_macro '- '())")[0],
__name__,
HyASTCompiler(__name__))
except HyTypeError as e:
assert "with the character `-`" in str(e)

View File

@ -0,0 +1,224 @@
(import
types
pytest)
(defn test-comprehension-types []
; Forms that get compiled to real comprehensions
(assert (is (type (lfor x "abc" x)) list))
(assert (is (type (sfor x "abc" x)) set))
(assert (is (type (dfor x "abc" [x x])) dict))
(assert (is (type (gfor x "abc" x)) types.GeneratorType))
; Forms that get compiled to loops
(assert (is (type (lfor x "abc" :do (setv y 1) x)) list))
(assert (is (type (sfor x "abc" :do (setv y 1) x)) set))
(assert (is (type (dfor x "abc" :do (setv y 1) [x x])) dict))
(assert (is (type (gfor x "abc" :do (setv y 1) x)) types.GeneratorType)))
#@ ((pytest.mark.parametrize "specialop" ["for" "lfor" "sfor" "gfor" "dfor"])
(defn test-fors [specialop]
(setv cases [
['(f x [] x)
[]]
['(f j [1 2 3] j)
[1 2 3]]
['(f x (range 3) (* x 2))
[0 2 4]]
['(f x (range 2) y (range 2) (, x y))
[(, 0 0) (, 0 1) (, 1 0) (, 1 1)]]
['(f (, x y) (.items {"1" 1 "2" 2}) (* y 2))
[2 4]]
['(f x (do (setv s "x") "ab") y (do (+= s "y") "def") (+ x y s))
["adxy" "aexy" "afxy" "bdxyy" "bexyy" "bfxyy"]]
['(f x (range 4) :if (% x 2) (* x 2))
[2 6]]
['(f x "abc" :setv y (.upper x) (+ x y))
["aA" "bB" "cC"]]
['(f x "abc" :do (setv y (.upper x)) (+ x y))
["aA" "bB" "cC"]]
['(f
x (range 3)
y (range 3)
:if (> y x)
z [7 8 9]
:setv s (+ x y z)
:if (!= z 8)
(, x y z s))
[(, 0 1 7 8) (, 0 1 9 10) (, 0 2 7 9) (, 0 2 9 11)
(, 1 2 7 10) (, 1 2 9 12)]]
['(f
x [0 1]
:setv l []
y (range 4)
:do (.append l (, x y))
:if (>= y 2)
z [7 8 9]
:if (!= z 8)
(, x y (tuple l) z))
[(, 0 2 (, (, 0 0) (, 0 1) (, 0 2)) 7)
(, 0 2 (, (, 0 0) (, 0 1) (, 0 2)) 9)
(, 0 3 (, (, 0 0) (, 0 1) (, 0 2) (, 0 3)) 7)
(, 0 3 (, (, 0 0) (, 0 1) (, 0 2) (, 0 3)) 9)
(, 1 2 (, (, 1 0) (, 1 1) (, 1 2)) 7)
(, 1 2 (, (, 1 0) (, 1 1) (, 1 2)) 9)
(, 1 3 (, (, 1 0) (, 1 1) (, 1 2) (, 1 3)) 7)
(, 1 3 (, (, 1 0) (, 1 1) (, 1 2) (, 1 3)) 9)]]
['(f x (range 4) :do (unless (% x 2) (continue)) (* x 2))
[2 6]]
['(f x (range 4) :setv p 9 :do (unless (% x 2) (continue)) (* x 2))
[2 6]]
['(f x (range 20) :do (when (= x 3) (break)) (* x 2))
[0 2 4]]
['(f x (range 20) :setv p 9 :do (when (= x 3) (break)) (* x 2))
[0 2 4]]
['(f x [4 5] y (range 20) :do (when (> y 1) (break)) z [8 9] (, x y z))
[(, 4 0 8) (, 4 0 9) (, 4 1 8) (, 4 1 9)
(, 5 0 8) (, 5 0 9) (, 5 1 8) (, 5 1 9)]]])
(for [[expr answer] cases]
; Mutate the case as appropriate for the operator before
; evaluating it.
(setv expr (+ (HyExpression [(HySymbol specialop)]) (cut expr 1)))
(when (= specialop "dfor")
(setv expr (+ (cut expr 0 -1) `([~(get expr -1) 1]))))
(when (= specialop "for")
(setv expr `(do
(setv out [])
(for [~@(cut expr 1 -1)]
(.append out ~(get expr -1)))
out)))
(setv result (eval expr))
(when (= specialop "dfor")
(setv result (.keys result)))
(assert (= (sorted result) answer) (str expr)))))
(defn test-fors-no-loopers []
(setv l [])
(for [] (.append l 1))
(assert (= l []))
(assert (= (lfor 1) []))
(assert (= (sfor 1) #{}))
(assert (= (list (gfor 1)) []))
(assert (= (dfor [1 2]) {})))
(defn test-raise-in-comp []
(defclass E [Exception] [])
(setv l [])
(import pytest)
(with [(pytest.raises E)]
(lfor
x (range 10)
:do (.append l x)
:do (when (= x 5)
(raise (E)))
x))
(assert (= l [0 1 2 3 4 5])))
(defn test-scoping []
(setv x 0)
(for [x [1 2 3]])
(assert (= x 3))
; An `lfor` that gets compiled to a real comprehension
(setv x 0)
(assert (= (lfor x [1 2 3] (inc x)) [2 3 4]))
(assert (= x 0))
; An `lfor` that gets compiled to a loop
(setv x 0 l [])
(assert (= (lfor x [4 5 6] :do (.append l 1) (inc x)) [5 6 7]))
(assert (= l [1 1 1]))
(assert (= x 0))
; An `sfor` that gets compiled to a real comprehension
(setv x 0)
(assert (= (sfor x [1 2 3] (inc x)) #{2 3 4}))
(assert (= x 0)))
(defn test-for-loop []
"NATIVE: test for loops"
(setv count1 0 count2 0)
(for [x [1 2 3 4 5]]
(setv count1 (+ count1 x))
(setv count2 (+ count2 x)))
(assert (= count1 15))
(assert (= count2 15))
(setv count 0)
(for [x [1 2 3 4 5]
y [1 2 3 4 5]]
(setv count (+ count x y))
(else
(+= count 1)))
(assert (= count 151))
(setv count 0)
; multiple statements in the else branch should work
(for [x [1 2 3 4 5]
y [1 2 3 4 5]]
(setv count (+ count x y))
(else
(+= count 1)
(+= count 10)))
(assert (= count 161))
; don't be fooled by constructs that look like else
(setv s "")
(setv else True)
(for [x "abcde"]
(+= s x)
[else (+= s "_")])
(assert (= s "a_b_c_d_e_"))
(setv s "")
(with [(pytest.raises TypeError)]
(for [x "abcde"]
(+= s x)
("else" (+= s "z"))))
(assert (= s "az"))
(assert (= (list ((fn [] (for [x [[1] [2 3]] y x] (yield y)))))
(lfor x [[1] [2 3]] y x y)))
(assert (= (list ((fn [] (for [x [[1] [2 3]] y x z (range 5)] (yield z)))))
(lfor x [[1] [2 3]] y x z (range 5) z))))
(defn test-nasty-for-nesting []
"NATIVE: test nesting for loops harder"
;; This test and feature is dedicated to @nedbat.
;; OK. This next test will ensure that we call the else branch exactly
;; once.
(setv flag 0)
(for [x (range 2)
y (range 2)]
(+ 1 1)
(else (setv flag (+ flag 2))))
(assert (= flag 2)))
(defn test-empty-for []
(setv l [])
(defn f []
(for [x (range 3)]
(.append l "a")
(yield x)))
(for [x (f)])
(assert (= l ["a" "a" "a"]))
(setv l [])
(for [x (f)]
(else (.append l "z")))
(assert (= l ["a" "a" "a" "z"])))

View File

@ -1,9 +1,9 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import
[hy._compat [PY3 PY36 PY37]]
[hy._compat [PY36 PY37]]
[math [isnan]]
[hy.contrib.hy-repr [hy-repr hy-repr-register]])
@ -79,10 +79,10 @@
(assert (is (type (get orig 1)) float))
(assert (is (type (get result 1)) HyFloat)))
(when PY3 (defn test-dict-views []
(defn test-dict-views []
(assert (= (hy-repr (.keys {1 2})) "(dict-keys [1])"))
(assert (= (hy-repr (.values {1 2})) "(dict-values [2])"))
(assert (= (hy-repr (.items {1 2})) "(dict-items [(, 1 2)])"))))
(assert (= (hy-repr (.items {1 2})) "(dict-items [(, 1 2)])")))
(defn test-datetime []
(import [datetime :as D])
@ -91,9 +91,8 @@
"(datetime.datetime 2009 1 15 15 27 5)"))
(assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 123))
"(datetime.datetime 2009 1 15 15 27 5 123)"))
(when PY3
(assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 123 :tzinfo D.timezone.utc))
"(datetime.datetime 2009 1 15 15 27 5 123 :tzinfo datetime.timezone.utc)")))
(assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 123 :tzinfo D.timezone.utc))
"(datetime.datetime 2009 1 15 15 27 5 123 :tzinfo datetime.timezone.utc)"))
(when PY36
(assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 :fold 1))
"(datetime.datetime 2009 1 15 15 27 5 :fold 1)"))
@ -114,17 +113,11 @@
(defn test-collections []
(import collections)
(assert (= (hy-repr (collections.defaultdict :a 8))
(if PY3
"(defaultdict None {\"a\" 8})"
"(defaultdict None {b\"a\" 8})")))
"(defaultdict None {\"a\" 8})"))
(assert (= (hy-repr (collections.defaultdict int :a 8))
(if PY3
"(defaultdict <class 'int'> {\"a\" 8})"
"(defaultdict <type 'int'> {b\"a\" 8})")))
"(defaultdict <class 'int'> {\"a\" 8})"))
(assert (= (hy-repr (collections.Counter [15 15 15 15]))
(if PY3
"(Counter {15 4})"
"(Counter {15 (int 4)})")))
"(Counter {15 4})"))
(setv C (collections.namedtuple "Fooey" ["cd" "a_b"]))
(assert (= (hy-repr (C 11 12))
"(Fooey :cd 11 :a_b 12)")))
@ -144,20 +137,19 @@
(setv x {1 2 3 [4 5] 6 7})
(setv (get x 3 1) x)
(assert (in (hy-repr x) (list-comp
(assert (in (hy-repr x) (lfor
; The ordering of a dictionary isn't guaranteed, so we need
; to check for all possible orderings.
(+ "{" (.join " " p) "}")
[p (permutations ["1 2" "3 [4 {...}]" "6 7"])]))))
p (permutations ["1 2" "3 [4 {...}]" "6 7"])
(+ "{" (.join " " p) "}")))))
(defn test-matchobject []
(import re)
(setv mo (re.search "b+" "aaaabbbccc"))
(assert (= (hy-repr mo)
(.format
#[[<{} object; :span {} :match "bbb">]]
(if PY37 "re.Match" (+ (. (type mo) __module__) ".SRE_Match"))
(if PY3 "(, 4 7)" "(, (int 4) (int 7))")))))
#[[<{} object; :span (, 4 7) :match "bbb">]]
(if PY37 "re.Match" (+ (. (type mo) __module__) ".SRE_Match"))))))
(defn test-hy-repr-custom []
@ -166,8 +158,8 @@
(assert (= (hy-repr (C)) "cuddles"))
(defclass Container [object]
[__init__ (fn [self value]
(setv self.value value))])
(defn __init__ [self value]
(setv self.value value)))
(hy-repr-register Container :placeholder "(Container ...)" (fn [x]
(+ "(Container " (hy-repr x.value) ")")))
(setv container (Container 5))
@ -178,5 +170,5 @@
(defn test-hy-repr-fallback []
(defclass D [object]
[__repr__ (fn [self] "cuddles")])
(defn __repr__ [self] "cuddles"))
(assert (= (hy-repr (D)) "cuddles")))

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.

View File

@ -1,123 +0,0 @@
;; Copyright 2018 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(require [hy.contrib.multi [defmulti defmethod default-method defn]])
(import pytest)
(defn test-different-signatures []
"NATIVE: Test multimethods with different signatures"
(defmulti fun [&rest args]
(len args))
(defmethod fun 0 []
"Hello!")
(defmethod fun 1 [a]
a)
(defmethod fun 2 [a b]
"a b")
(defmethod fun 3 [a b c]
"a b c")
(assert (= (fun) "Hello!"))
(assert (= (fun "a") "a"))
(assert (= (fun "a" "b") "a b"))
(assert (= (fun "a" "b" "c") "a b c")))
(defn test-different-signatures-defn []
"NATIVE: Test defn with different signatures"
(defn f1
([] "")
([a] "a")
([a b] "a b"))
(assert (= (f1) ""))
(assert (= (f1 "a") "a"))
(assert (= (f1 "a" "b") "a b"))
(with [(pytest.raises TypeError)]
(f1 "a" "b" "c")))
(defn test-basic-dispatch []
"NATIVE: Test basic dispatch"
(defmulti area [shape]
(:type shape))
(defmethod area "square" [square]
(* (:width square)
(:height square)))
(defmethod area "circle" [circle]
(* (** (:radius circle) 2)
3.14))
(default-method area [shape]
0)
(assert (< 0.784 (area {:type "circle" :radius 0.5}) 0.786))
(assert (= (area {:type "square" :width 2 :height 2})) 4)
(assert (= (area {:type "non-euclid rhomboid"}) 0)))
(defn test-docs []
"NATIVE: Test if docs are properly handled"
(defmulti fun [a b]
"docs"
a)
(defmethod fun "foo" [a b]
"foo was called")
(defmethod fun "bar" [a b]
"bar was called")
(assert (= fun.--doc-- "docs")))
(defn test-kwargs-handling []
"NATIVE: Test handling of kwargs with multimethods"
(defmulti fun [&kwargs kwargs]
(get kwargs "type"))
(defmethod fun "foo" [&kwargs kwargs]
"foo was called")
(defmethod fun "bar" [&kwargs kwargs]
"bar was called")
(assert (= (fun :type "foo" :extra "extra") "foo was called")))
(defn test-basic-multi []
"NATIVE: Test a basic arity overloaded defn"
(defn f2
([] "Hello!")
([a] a)
([a b] "a b")
([a b c] "a b c"))
(assert (= (f2) "Hello!"))
(assert (= (f2 "a") "a"))
(assert (= (f2 "a" "b") "a b"))
(assert (= (f2 "a" "b" "c") "a b c")))
(defn test-kw-args []
"NATIVE: Test if kwargs are handled correctly for arity overloading"
(defn f3
([a] a)
([&optional [a "nop"] [b "p"]] (+ a b)))
(assert (= (f3 1) 1))
(assert (= (f3 :a "t") "t"))
(assert (= (f3 "hello " :b "world") "hello world"))
(assert (= (f3 :a "hello " :b "world") "hello world")))
(defn test-docs []
"NATIVE: Test if docs are properly handled for arity overloading"
(defn f4
"docs"
([a] (print a))
([a b] (print b)))
(assert (= f4.--doc-- "docs")))

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -21,17 +21,16 @@
walk-form)))
(defn test-walk []
(setv acc '())
(assert (= (walk (partial collector acc) identity walk-form)
(setv acc [])
(assert (= (list (walk (partial collector acc) identity walk-form))
[None None]))
(assert (= acc walk-form))
(assert (= acc (list walk-form)))
(setv acc [])
(assert (= (walk identity (partial collector acc) walk-form)
None))
(assert (= acc [walk-form])))
(defn test-walk-iterators []
(setv acc [])
(assert (= (walk (fn [x] (* 2 x)) (fn [x] x)
(drop 1 [1 [2 [3 [4]]]]))
[[2 [3 [4]] 2 [3 [4]]]])))
@ -44,19 +43,27 @@
(assert (= (macroexpand-all '(foo-walk))
42))
(assert (= (macroexpand-all '(with [a 1]))
'(with* [a 1] (do))))
'(with* [a 1])))
(assert (= (macroexpand-all '(with [a 1 b 2 c 3] (for [d c] foo)))
'(with* [a 1] (with* [b 2] (with* [c 3] (do (for* [d c] (do foo))))))))
'(with* [a 1] (with* [b 2] (with* [c 3] (for [d c] foo))))))
(assert (= (macroexpand-all '(with [a 1]
'(with [b 2])
`(with [c 3]
~(with [d 4])
~@[(with [e 5])])))
'(with* [a 1]
(do '(with [b 2])
`(with [c 3]
~(with* [d 4] (do))
~@[(with* [e 5] (do))]))))))
'(with [b 2])
`(with [c 3]
~(with* [d 4])
~@[(with* [e 5])]))))
(defmacro require-macro []
`(do
(require [tests.resources.macros [test-macro :as my-test-macro]])
(my-test-macro)))
(assert (= (last (macroexpand-all '(require-macro)))
'(setv blah 1))))
(defn test-let-basic []
(assert (zero? (let [a 0] a)))
@ -64,16 +71,16 @@
b "b")
(let [a "x"
b "y"]
(assert (= (+ a b)
"xy"))
(let [a "z"]
(assert (= (+ a b)
"zy")))
;; let-shadowed variable doesn't get clobbered.
(assert (= (+ a b)
"xy")))
(assert (= (+ a b)
"xy"))
(let [a "z"]
(assert (= (+ a b)
"zy")))
;; let-shadowed variable doesn't get clobbered.
(assert (= (+ a b)
"xy")))
(let [q "q"]
(assert (= q "q")))
(assert (= q "q")))
(assert (= a "a"))
(assert (= b "b"))
(assert (in "a" (.keys (vars))))
@ -85,86 +92,86 @@
(let [a "a"
b "b"
ab (+ a b)]
(assert (= ab "ab"))
(let [c "c"
abc (+ ab c)]
(assert (= abc "abc")))))
(assert (= ab "ab"))
(let [c "c"
abc (+ ab c)]
(assert (= abc "abc")))))
(defn test-let-early []
(setv a "a")
(let [q (+ a "x")
a 2 ; should not affect q
b 3]
(assert (= q "ax"))
(let [q (* a b)
a (+ a b)
b (* a b)]
(assert (= q 6))
(assert (= a 5))
(assert (= b 15))))
(assert (= q "ax"))
(let [q (* a b)
a (+ a b)
b (* a b)]
(assert (= q 6))
(assert (= a 5))
(assert (= b 15))))
(assert (= a "a")))
(defn test-let-special []
;; special forms in function position still work as normal
(let [, 1]
(assert (= (, , ,)
(, 1 1)))))
(assert (= (, , ,)
(, 1 1)))))
(defn test-let-quasiquote []
(setv a-symbol 'a)
(let [a "x"]
(assert (= a "x"))
(assert (= 'a a-symbol))
(assert (= `a a-symbol))
(assert (= `(foo ~a)
'(foo "x")))
(assert (= `(foo `(bar a ~a ~~a))
'(foo `(bar a ~a ~"x"))))
(assert (= `(foo ~@[a])
'(foo "x")))
(assert (= `(foo `(bar [a] ~@[a] ~@~[a 'a `a] ~~@[a]))
'(foo `(bar [a] ~@[a] ~@["x" a a] ~"x"))))))
(assert (= a "x"))
(assert (= 'a a-symbol))
(assert (= `a a-symbol))
(assert (= `(foo ~a)
'(foo "x")))
(assert (= `(foo `(bar a ~a ~~a))
'(foo `(bar a ~a ~"x"))))
(assert (= `(foo ~@[a])
'(foo "x")))
(assert (= `(foo `(bar [a] ~@[a] ~@~(HyList [a 'a `a]) ~~@[a]))
'(foo `(bar [a] ~@[a] ~@["x" a a] ~"x"))))))
(defn test-let-except []
(let [foo 42
bar 33]
(assert (= foo 42))
(try
(do
1/0
(assert False))
(except [foo Exception]
;; let bindings should work in except block
(assert (= bar 33))
;; but exception bindings can shadow let bindings
(assert (instance? Exception foo))))
;; let binding did not get clobbered.
(assert (= foo 42))))
(assert (= foo 42))
(try
(do
1/0
(assert False))
(except [foo Exception]
;; let bindings should work in except block
(assert (= bar 33))
;; but exception bindings can shadow let bindings
(assert (instance? Exception foo))))
;; let binding did not get clobbered.
(assert (= foo 42))))
(defn test-let-mutation []
(setv foo 42)
(setv error False)
(let [foo 12
bar 13]
(assert (= foo 12))
(setv foo 14)
(assert (= foo 14))
(del foo)
;; deleting a let binding should not affect others
(assert (= bar 13))
(try
;; foo=42 is still shadowed, but the let binding was deleted.
(do
foo
(assert False))
(except [le LookupError]
(setv error le)))
(setv foo 16)
(assert (= foo 16))
(setv [foo bar baz] [1 2 3])
(assert (= foo 1))
(assert (= bar 2))
(assert (= baz 3)))
(assert (= foo 12))
(setv foo 14)
(assert (= foo 14))
(del foo)
;; deleting a let binding should not affect others
(assert (= bar 13))
(try
;; foo=42 is still shadowed, but the let binding was deleted.
(do
foo
(assert False))
(except [le LookupError]
(setv error le)))
(setv foo 16)
(assert (= foo 16))
(setv [foo bar baz] [1 2 3])
(assert (= foo 1))
(assert (= bar 2))
(assert (= baz 3)))
(assert error)
(assert (= foo 42))
(assert (= baz 3)))
@ -172,40 +179,40 @@
(defn test-let-break []
(for [x (range 3)]
(let [done (odd? x)]
(if done (break))))
(if done (break))))
(assert (= x 1)))
(defn test-let-continue []
(let [foo []]
(for [x (range 10)]
(let [odd (odd? x)]
(if odd (continue))
(.append foo x)))
(assert (= foo [0 2 4 6 8]))))
(for [x (range 10)]
(let [odd (odd? x)]
(if odd (continue))
(.append foo x)))
(assert (= foo [0 2 4 6 8]))))
(defn test-let-yield []
(defn grind []
(yield 0)
(let [a 1
b 2]
(yield a)
(yield b)))
(yield a)
(yield b)))
(assert (= (tuple (grind))
(, 0 1 2))))
(defn test-let-return []
(defn get-answer []
(let [answer 42]
(return answer)))
(return answer)))
(assert (= (get-answer)
42)))
(defn test-let-import []
(let [types 6]
;; imports don't fail, even if using a let-bound name
(import types)
;; let-bound name is not affected
(assert (= types 6)))
;; imports don't fail, even if using a let-bound name
(import types)
;; let-bound name is not affected
(assert (= types 6)))
;; import happened in Python scope.
(assert (in "types" (vars)))
(assert (instance? types.ModuleType types)))
@ -213,13 +220,13 @@
(defn test-let-defclass []
(let [Foo 42
quux object]
;; the name of the class is just a symbol, even if it's a let binding
(defclass Foo [quux] ; let bindings apply in inheritance list
;; let bindings apply inside class body
(setv x Foo)
;; quux is not local
(setv quux "quux"))
(assert (= quux "quux")))
;; the name of the class is just a symbol, even if it's a let binding
(defclass Foo [quux] ; let bindings apply in inheritance list
;; let bindings apply inside class body
(setv x Foo)
;; quux is not local
(setv quux "quux"))
(assert (= quux "quux")))
;; defclass always creates a python-scoped variable, even if it's a let binding name
(assert (= Foo.x 42)))
@ -229,82 +236,82 @@
(let [a 1
b []
bar (fn [])]
(setv bar.a 13)
(assert (= bar.a 13))
(setv (. bar a) 14)
(assert (= bar.a 14))
(assert (= a 1))
(assert (= b []))
;; method syntax not affected
(.append b 2)
(assert (= b [2]))
;; attrs access is not affected
(assert (= foo.a 42))
(assert (= (. foo a)
42))
;; but indexing is
(assert (= (. [1 2 3]
[a])
2))))
(setv bar.a 13)
(assert (= bar.a 13))
(setv (. bar a) 14)
(assert (= bar.a 14))
(assert (= a 1))
(assert (= b []))
;; method syntax not affected
(.append b 2)
(assert (= b [2]))
;; attrs access is not affected
(assert (= foo.a 42))
(assert (= (. foo a)
42))
;; but indexing is
(assert (= (. [1 2 3]
[a])
2))))
(defn test-let-positional []
(let [a 0
b 1
c 2]
(defn foo [a b]
(, a b c))
(assert (= (foo 100 200)
(, 100 200 2)))
(setv c 300)
(assert (= (foo 1000 2000)
(, 1000 2000 300)))
(assert (= a 0))
(assert (= b 1))
(assert (= c 300))))
(defn foo [a b]
(, a b c))
(assert (= (foo 100 200)
(, 100 200 2)))
(setv c 300)
(assert (= (foo 1000 2000)
(, 1000 2000 300)))
(assert (= a 0))
(assert (= b 1))
(assert (= c 300))))
(defn test-let-rest []
(let [xs 6
a 88
c 64
&rest 12]
(defn foo [a b &rest xs]
(-= a 1)
(setv xs (list xs))
(.append xs 42)
(, &rest a b c xs))
(assert (= xs 6))
(assert (= a 88))
(assert (= (foo 1 2 3 4)
(, 12 0 2 64 [3 4 42])))
(assert (= xs 6))
(assert (= c 64))
(assert (= a 88))))
(defn foo [a b &rest xs]
(-= a 1)
(setv xs (list xs))
(.append xs 42)
(, &rest a b c xs))
(assert (= xs 6))
(assert (= a 88))
(assert (= (foo 1 2 3 4)
(, 12 0 2 64 [3 4 42])))
(assert (= xs 6))
(assert (= c 64))
(assert (= a 88))))
(defn test-let-kwargs []
(let [kws 6
&kwargs 13]
(defn foo [&kwargs kws]
(, &kwargs kws))
(assert (= kws 6))
(assert (= (foo :a 1)
(, 13 {"a" 1})))))
(defn foo [&kwargs kws]
(, &kwargs kws))
(assert (= kws 6))
(assert (= (foo :a 1)
(, 13 {"a" 1})))))
(defn test-let-optional []
(let [a 1
b 6
d 2]
(defn foo [&optional [a a] b [c d]]
(, a b c))
(assert (= (foo)
(, 1 None 2)))
(assert (= (foo 10 20 30)
(, 10 20 30)))))
(defn foo [&optional [a a] b [c d]]
(, a b c))
(assert (= (foo)
(, 1 None 2)))
(assert (= (foo 10 20 30)
(, 10 20 30)))))
(defn test-let-closure []
(let [count 0]
(defn +count [&optional [x 1]]
(+= count x)
count))
(defn +count [&optional [x 1]]
(+= count x)
count))
;; let bindings can still exist outside of a let body
(assert (= 1 (+count)))
(assert (= 2 (+count)))
@ -323,9 +330,18 @@
(let [a 1
b (triple a)
c (ap-triple)]
(assert (= (triple a)
3))
(assert (= (ap-triple)
3))
(assert (= b 3))
(assert (= c 3))))
(assert (= (triple a)
3))
(assert (= (ap-triple)
3))
(assert (= b 3))
(assert (= c 3))))
(defn test-let-rebind []
(let [x "foo"
y "bar"
x (+ x y)
y (+ y x)
x (+ x x)]
(assert (= x "foobarfoobar"))
(assert (= y "barfoobar"))))

View File

@ -1,9 +1,7 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import [hy._compat [PY3]])
;;;; some simple helpers
(defn assert-true [x]
@ -267,15 +265,25 @@ result['y in globals'] = 'y' in globals()")
(assert-true (symbol? 'im-symbol))
(assert-false (symbol? (name 'im-symbol))))
(defn test-list? []
"NATIVE: testing the list? function"
(assert-false (list? "hello"))
(assert-true (list? [1 2 3])))
(defn test-tuple? []
"NATIVE: testing the tuple? function"
(assert-false (tuple? [4 5]))
(assert-true (tuple? (, 4 5))))
(defn test-gensym []
"NATIVE: testing the gensym function"
(import [hy.models [HySymbol]])
(setv s1 (gensym))
(assert (isinstance s1 HySymbol))
(assert (= 0 (.find s1 "_;G|")))
(assert (= 0 (.find s1 "_G\uffff")))
(setv s2 (gensym "xx"))
(setv s3 (gensym "xx"))
(assert (= 0 (.find s2 "_;xx|")))
(assert (= 0 (.find s2 "_xx\uffff")))
(assert (not (= s2 s3)))
(assert (not (= (str s2) (str s3)))))
@ -292,7 +300,7 @@ result['y in globals'] = 'y' in globals()")
(assert-requires-num inc)
(defclass X [object]
[__add__ (fn [self other] (.format "__add__ got {}" other))])
(defn __add__ [self other] (.format "__add__ got {}" other)))
(assert-equal (inc (X)) "__add__ got 1"))
(defn test-instance []
@ -314,8 +322,8 @@ result['y in globals'] = 'y' in globals()")
(assert-true (integer? 0))
(assert-true (integer? 3))
(assert-true (integer? -3))
(assert-true (integer? (integer "-3")))
(assert-true (integer? (integer 3)))
(assert-true (integer? (int "-3")))
(assert-true (integer? (int 3)))
(assert-false (integer? 4.2))
(assert-false (integer? None))
(assert-false (integer? "foo")))
@ -324,7 +332,7 @@ result['y in globals'] = 'y' in globals()")
"NATIVE: testing the integer-char? function"
(assert-true (integer-char? "1"))
(assert-true (integer-char? "-1"))
(assert-true (integer-char? (str (integer 300))))
(assert-true (integer-char? (str (int 300))))
(assert-false (integer-char? "foo"))
(assert-false (integer-char? None)))
@ -419,8 +427,7 @@ result['y in globals'] = 'y' in globals()")
(assert-true (neg? -2))
(assert-false (neg? 1))
(assert-false (neg? 0))
(when PY3
(assert-requires-num neg?)))
(assert-requires-num neg?))
(defn test-zero []
"NATIVE: testing the zero? function"
@ -471,6 +478,15 @@ result['y in globals'] = 'y' in globals()")
(assert-false (odd? 0))
(assert-requires-num odd?))
(defn test-parse-args []
"NATIVE: testing the parse-args function"
(setv parsed-args (parse-args [["strings" :nargs "+" :help "Strings"]
["-n" "--numbers" :action "append" :type 'int :help "Numbers"]]
["a" "b" "-n" "1" "-n" "2"]
:description "Parse strings and numbers from args"))
(assert-equal parsed-args.strings ["a" "b"])
(assert-equal parsed-args.numbers [1 2]))
(defn test-partition []
"NATIVE: testing the partition function"
(setv ten (range 10))
@ -509,8 +525,7 @@ result['y in globals'] = 'y' in globals()")
(assert-true (pos? 2))
(assert-false (pos? -1))
(assert-false (pos? 0))
(when PY3
(assert-requires-num pos?)))
(assert-requires-num pos?))
(defn test-remove []
"NATIVE: testing the remove function"
@ -628,8 +643,8 @@ result['y in globals'] = 'y' in globals()")
"NATIVE: testing the keyword? function"
(assert (keyword? ':bar))
(assert (keyword? ':baz))
(assert (keyword? :bar))
(assert (keyword? :baz))
(setv x :bar)
(assert (keyword? x))
(assert (not (keyword? "foo")))
(assert (not (keyword? ":foo")))
(assert (not (keyword? 1)))
@ -682,3 +697,10 @@ result['y in globals'] = 'y' in globals()")
(defn test-comment []
(assert-none (comment <h1>This is merely a comment.</h1>
<p> Move along. (Nothing to see here.)</p>)))
(defn test-doc [capsys]
(doc doc)
(setv out_err (.readouterr capsys))
(assert (.startswith (.strip (first out_err))
"Help on function doc in module hy.core.macros:"))
(assert (empty? (second out_err))))

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -27,7 +27,7 @@
(defn test-defclass-attrs []
"NATIVE: test defclass attributes"
(defclass A []
[x 42])
(setv x 42))
(assert (= A.x 42))
(assert (= (getattr (A) "x") 42)))
@ -35,9 +35,9 @@
(defn test-defclass-attrs-fn []
"NATIVE: test defclass attributes with fn"
(defclass B []
[x 42
y (fn [self value]
(+ self.x value))])
(setv x 42)
(setv y (fn [self value]
(+ self.x value))))
(assert (= B.x 42))
(assert (= (.y (B) 5) 47))
(setv b (B))
@ -48,17 +48,17 @@
(defn test-defclass-dynamic-inheritance []
"NATIVE: test defclass with dynamic inheritance"
(defclass A [((fn [] (if True list dict)))]
[x 42])
(setv x 42))
(assert (isinstance (A) list))
(defclass A [((fn [] (if False list dict)))]
[x 42])
(setv x 42))
(assert (isinstance (A) dict)))
(defn test-defclass-no-fn-leak []
"NATIVE: test defclass attributes with fn"
(defclass A []
[x (fn [] 1)])
(setv x (fn [] 1)))
(try
(do
(x)
@ -68,13 +68,13 @@
(defn test-defclass-docstring []
"NATIVE: test defclass docstring"
(defclass A []
[--doc-- "doc string"
x 1])
(setv --doc-- "doc string")
(setv x 1))
(setv a (A))
(assert (= a.__doc__ "doc string"))
(defclass B []
"doc string"
[x 1])
(setv x 1))
(setv b (B))
(assert (= b.x 1))
(assert (= b.__doc__ "doc string"))
@ -82,7 +82,7 @@
"begin a very long multi-line string to make
sure that it comes out the way we hope
and can span 3 lines end."
[x 1])
(setv x 1))
(setv mL (MultiLine))
(assert (= mL.x 1))
(assert (in "begin" mL.__doc__))
@ -100,8 +100,8 @@
"NATIVE: test defclass syntax with properties and methods and side-effects"
(setv foo 1)
(defclass A []
[x 1
y 2]
(setv x 1)
(setv y 2)
(global foo)
(setv foo 2)
(defn greet [self]
@ -117,7 +117,7 @@
(defn test-defclass-implicit-none-for-init []
"NATIVE: test that defclass adds an implicit None to --init--"
(defclass A []
[--init-- (fn [self] (setv self.x 1) 42)])
(setv --init-- (fn [self] (setv self.x 1) 42)))
(defclass B []
(defn --init-- [self]
(setv self.x 2)

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.

View File

@ -1,14 +1,13 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import [hy.extra.reserved [names]] [hy._compat [PY3]])
(import [hy.extra.reserved [names]])
(defn test-reserved []
(assert (is (type (names)) frozenset))
(assert (in "and" (names)))
(when PY3
(assert (in "False" (names))))
(assert (in "False" (names)))
(assert (in "pass" (names)))
(assert (in "class" (names)))
(assert (in "defclass" (names)))

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -7,11 +7,11 @@
[sys :as systest]
re
[operator [or_]]
[hy.errors [HyTypeError]]
[hy.errors [HyLanguageError]]
pytest)
(import sys)
(import [hy._compat [PY3 PY35 PY37]])
(import [hy._compat [PY38]])
(defn test-sys-argv []
"NATIVE: test sys.argv"
@ -68,16 +68,15 @@
"NATIVE: test that setv doesn't work on names Python can't assign to
and that we can't mangle"
(try (eval '(setv None 1))
(except [e [TypeError]] (assert (in "Can't assign to" (str e)))))
(except [e [SyntaxError]] (assert (in "illegal target for assignment" (str e)))))
(try (eval '(defn None [] (print "hello")))
(except [e [TypeError]] (assert (in "Can't assign to" (str e)))))
(when PY3
(try (eval '(setv False 1))
(except [e [TypeError]] (assert (in "Can't assign to" (str e)))))
(try (eval '(setv True 0))
(except [e [TypeError]] (assert (in "Can't assign to" (str e)))))
(try (eval '(defn True [] (print "hello")))
(except [e [TypeError]] (assert (in "Can't assign to" (str e)))))))
(except [e [SyntaxError]] (assert (in "illegal target for assignment" (str e)))))
(try (eval '(setv False 1))
(except [e [SyntaxError]] (assert (in "illegal target for assignment" (str e)))))
(try (eval '(setv True 0))
(except [e [SyntaxError]] (assert (in "illegal target for assignment" (str e)))))
(try (eval '(defn True [] (print "hello")))
(except [e [SyntaxError]] (assert (in "illegal target for assignment" (str e))))))
(defn test-setv-pairs []
@ -87,7 +86,7 @@
(assert (= b 2))
(setv y 0 x 1 y x)
(assert (= y 1))
(with [(pytest.raises HyTypeError)]
(with [(pytest.raises HyLanguageError)]
(eval '(setv a 1 b))))
@ -130,8 +129,8 @@
(assert (none? (setv (get {} "x") 42)))
(setv l [])
(defclass Foo [object]
[__setattr__ (fn [self attr val]
(.append l [attr val]))])
(defn __setattr__ [self attr val]
(.append l [attr val])))
(setv x (Foo))
(assert (none? (setv x.eggs "ham")))
(assert (not (hasattr x "eggs")))
@ -144,141 +143,36 @@
(do
(eval '(setv (do 1 2) 1))
(assert False))
(except [e HyTypeError]
(assert (= e.message "Can't assign or delete a non-expression"))))
(except [e HyLanguageError]
(assert (= e.msg "Can't assign or delete a non-expression"))))
(try
(do
(eval '(setv 1 1))
(assert False))
(except [e HyTypeError]
(assert (= e.message "Can't assign or delete a HyInteger"))))
(except [e HyLanguageError]
(assert (= e.msg "Can't assign or delete a HyInteger"))))
(try
(do
(eval '(setv {1 2} 1))
(assert False))
(except [e HyTypeError]
(assert (= e.message "Can't assign or delete a HyDict"))))
(except [e HyLanguageError]
(assert (= e.msg "Can't assign or delete a HyDict"))))
(try
(do
(eval '(del 1 1))
(assert False))
(except [e HyTypeError]
(assert (= e.message "Can't assign or delete a HyInteger")))))
(except [e HyLanguageError]
(assert (= e.msg "Can't assign or delete a HyInteger")))))
(defn test-no-str-as-sym []
"Don't treat strings as symbols in the calling position"
(with [(pytest.raises TypeError)] ("setv" True 3)) ; A special form
(with [(pytest.raises TypeError)] ("abs" -2)) ; A function
(with [(pytest.raises TypeError)] ("when" 1 2)) ; A macro
None) ; Avoid https://github.com/hylang/hy/issues/1320
(defn test-for-loop []
"NATIVE: test for loops"
(setv count1 0 count2 0)
(for [x [1 2 3 4 5]]
(setv count1 (+ count1 x))
(setv count2 (+ count2 x)))
(assert (= count1 15))
(assert (= count2 15))
(setv count 0)
(for [x [1 2 3 4 5]
y [1 2 3 4 5]]
(setv count (+ count x y))
(else
(+= count 1)))
(assert (= count 151))
(setv count 0)
; multiple statements in the else branch should work
(for [x [1 2 3 4 5]
y [1 2 3 4 5]]
(setv count (+ count x y))
(else
(+= count 1)
(+= count 10)))
(assert (= count 161))
; don't be fooled by constructs that look like else
(setv s "")
(setv else True)
(for [x "abcde"]
(+= s x)
[else (+= s "_")])
(assert (= s "a_b_c_d_e_"))
(setv s "")
(setv else True)
(with [(pytest.raises TypeError)]
(for [x "abcde"]
(+= s x)
("else" (+= s "z"))))
(assert (= s "az"))
(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)])))
(setv l [])
(defn f []
(for [x [4 9 2]]
(.append l (* 10 x))
(yield x)))
(for [_ (f)])
(assert (= l [40 90 20])))
(defn test-nasty-for-nesting []
"NATIVE: test nesting for loops harder"
;; This test and feature is dedicated to @nedbat.
;; let's ensure empty iterating is an implicit do
(setv t 0)
(for [] (setv t 1))
(assert (= t 1))
;; OK. This first test will ensure that the else is hooked up to the
;; for when we break out of it.
(for [x (range 2)
y (range 2)]
(break)
(else (raise Exception)))
;; OK. This next test will ensure that the else is hooked up to the
;; "inner" iteration
(for [x (range 2)
y (range 2)]
(if (= y 1) (break))
(else (raise Exception)))
;; OK. This next test will ensure that the else is hooked up to the
;; "outer" iteration
(for [x (range 2)
y (range 2)]
(if (= x 1) (break))
(else (raise Exception)))
;; OK. This next test will ensure that we call the else branch exactly
;; once.
(setv flag 0)
(for [x (range 2)
y (range 2)]
(+ 1 1)
(else (setv flag (+ flag 2))))
(assert (= flag 2))
(setv l [])
(defn f []
(for [x [4 9 2]]
(.append l (* 10 x))
(yield x)))
(for [_ (f)])
(assert (= l [40 90 20])))
(with [(pytest.raises TypeError)] ("when" 1 2))) ; A macro
(defn test-while-loop []
@ -303,7 +197,23 @@
(.append l 1)
(len l))
(while (!= (f) 4) (do))
(assert (= l [1 1 1 1])))
(assert (= l [1 1 1 1]))
; only compile the condition once
; https://github.com/hylang/hy/issues/1790
(global while-cond-var)
(setv while-cond-var 10)
(eval
'(do
(defmacro while-cond []
(global while-cond-var)
(assert (= while-cond-var 10))
(+= while-cond-var 1)
`(do
(setv x 3)
False))
(while (while-cond))
(assert (= x 3)))))
(defn test-while-loop-else []
(setv count 5)
@ -549,9 +459,9 @@
(defclass X [object] [])
(defclass M [object]
[meth (fn [self &rest args &kwargs kwargs]
(defn meth [self &rest args &kwargs kwargs]
(.join " " (+ (, "meth") args
(tuple (map (fn [k] (get kwargs k)) (sorted (.keys kwargs)))))))])
(tuple (map (fn [k] (get kwargs k)) (sorted (.keys kwargs))))))))
(setv x (X))
(setv m (M))
@ -618,9 +528,7 @@
(setv passed False)
(try
(raise)
;; Python 2 raises IndexError here (due to the previous test)
;; Python 3 raises RuntimeError
(except [[IndexError RuntimeError]]
(except [RuntimeError]
(setv passed True)))
(assert passed)
@ -852,16 +760,11 @@
(defn test-yield-with-return []
"NATIVE: test yield with return"
(defn gen [] (yield 3) "goodbye")
(if PY3
(do (setv gg (gen))
(assert (= 3 (next gg)))
(try (next gg)
(except [e StopIteration] (assert (hasattr e "value"))
(assert (= (getattr e "value") "goodbye")))))
(do (setv gg (gen))
(assert (= 3 (next gg)))
(try (next gg)
(except [e StopIteration] (assert (not (hasattr e "value"))))))))
(setv gg (gen))
(assert (= 3 (next gg)))
(try (next gg)
(except [e StopIteration] (assert (hasattr e "value"))
(assert (= (getattr e "value") "goodbye")))))
(defn test-yield-in-try []
@ -935,64 +838,18 @@
(defn test-for-else []
"NATIVE: test for else"
(setv x 0)
(for* [a [1 2]]
(for [a [1 2]]
(setv x (+ x a))
(else (setv x (+ x 50))))
(assert (= x 53))
(setv x 0)
(for* [a [1 2]]
(for [a [1 2]]
(setv x (+ x a))
(else))
(assert (= x 3)))
(defn test-list-comprehensions []
"NATIVE: test list comprehensions"
(assert (= (list-comp (* x 2) [x (range 2)]) [0 2]))
(assert (= (list-comp (* x 2) [x (range 4)] (% x 2)) [2 6]))
(assert (= (sorted (list-comp (* y 2) [(, x y) (.items {"1" 1 "2" 2})]))
[2 4]))
(assert (= (list-comp (, x y) [x (range 2) y (range 2)])
[(, 0 0) (, 0 1) (, 1 0) (, 1 1)]))
(assert (= (list-comp j [j [1 2]]) [1 2])))
(defn test-set-comprehensions []
"NATIVE: test set comprehensions"
(assert (instance? set (set-comp x [x (range 2)])))
(assert (= (set-comp (* x 2) [x (range 2)]) (set [0 2])))
(assert (= (set-comp (* x 2) [x (range 4)] (% x 2)) (set [2 6])))
(assert (= (set-comp (* y 2) [(, x y) (.items {"1" 1 "2" 2})])
(set [2 4])))
(assert (= (set-comp (, x y) [x (range 2) y (range 2)])
(set [(, 0 0) (, 0 1) (, 1 0) (, 1 1)])))
(assert (= (set-comp j [j [1 2]]) (set [1 2]))))
(defn test-dict-comprehensions []
"NATIVE: test dict comprehensions"
(assert (instance? dict (dict-comp x x [x (range 2)])))
(assert (= (dict-comp x (* x 2) [x (range 2)]) {1 2 0 0}))
(assert (= (dict-comp x (* x 2) [x (range 4)] (% x 2)) {3 6 1 2}))
(assert (= (dict-comp x (* y 2) [(, x y) (.items {"1" 1 "2" 2})])
{"2" 4 "1" 2}))
(assert (= (dict-comp (, x y) (+ x y) [x (range 2) y (range 2)])
{(, 0 0) 0 (, 1 0) 1 (, 0 1) 1 (, 1 1) 2})))
(defn test-generator-expressions []
"NATIVE: test generator expressions"
(assert (not (instance? list (genexpr x [x (range 2)]))))
(assert (= (list (genexpr (* x 2) [x (range 2)])) [0 2]))
(assert (= (list (genexpr (* x 2) [x (range 4)] (% x 2))) [2 6]))
(assert (= (list (sorted (genexpr (* y 2) [(, x y) (.items {"1" 1 "2" 2})])))
[2 4]))
(assert (= (list (genexpr (, x y) [x (range 2) y (range 2)]))
[(, 0 0) (, 0 1) (, 1 0) (, 1 1)]))
(assert (= (list (genexpr j [j [1 2]])) [1 2])))
(defn test-defn-order []
"NATIVE: test defn evaluation order"
(setv acc [])
@ -1051,6 +908,22 @@
(assert (= mooey.__name__ "mooey")))
(defn test-defn-annotations []
"NATIVE: test that annotations in defn work"
(defn f [^int p1 p2 ^str p3 &optional ^str o1 ^int [o2 0]
&rest ^str rest &kwonly ^str k1 ^int [k2 0] &kwargs ^bool kwargs])
(assert (= (. f __annotations__ ["p1"]) int))
(assert (= (. f __annotations__ ["p3"]) str))
(assert (= (. f __annotations__ ["o1"]) str))
(assert (= (. f __annotations__ ["o2"]) int))
(assert (= (. f __annotations__ ["rest"]) str))
(assert (= (. f __annotations__ ["k1"]) str))
(assert (= (. f __annotations__ ["k2"]) int))
(assert (= (. f __annotations__ ["kwargs"]) bool)))
(defn test-return []
; `return` in main line
@ -1247,7 +1120,8 @@
(assert (= :foo :foo))
(assert (= :foo ':foo))
(assert (is (type :foo) (type ':foo)))
(setv x :foo)
(assert (is (type x) (type ':foo)))
(assert (= (get {:foo "bar"} :foo) "bar"))
(assert (= (get {:bar "quux"} (get {:foo :bar} :foo)) "quux")))
@ -1262,9 +1136,9 @@
(defn test-empty-keyword []
"NATIVE: test that the empty keyword is recognized"
(assert (= : :))
(assert (keyword? :))
(assert (keyword? ':))
(assert (!= : ":"))
(assert (= (name :) "")))
(assert (= (name ':) "")))
(defn test-nested-if []
@ -1341,12 +1215,15 @@
5j 5.1j 2+1j 1.2+3.4j
"" b""
"apple bloom" b"apple bloom" "⚘" b"\x00"
:mykeyword
[] #{} {}
[1 2 3] #{1 2 3} {"a" 1 "b" 2}]]
(assert (= (eval `(identity ~x)) x))
(assert (= (eval x) x)))
(setv kw :mykeyword)
(assert (= (get (eval `[~kw]) 0) kw))
(assert (= (eval kw) kw))
; Tuples wrap to HyLists, not HyExpressions.
(assert (= (eval (,)) []))
(assert (= (eval (, 1 2 3)) [1 2 3]))
@ -1368,6 +1245,67 @@
(assert (none? (. '"squid" brackets))))
(defn test-format-strings []
(assert (= f"hello world" "hello world"))
(assert (= f"hello {(+ 1 1)} world" "hello 2 world"))
(assert (= f"a{ (.upper (+ \"g\" \"k\")) }z" "aGKz"))
; Referring to a variable
(setv p "xyzzy")
(assert (= f"h{p}j" "hxyzzyj"))
; Including a statement and setting a variable
(assert (= f"a{(do (setv floop 4) (* floop 2))}z" "a8z"))
(assert (= floop 4))
; Comments
(assert (= f"a{(+ 1
2 ; This is a comment.
3)}z" "a6z"))
; Newlines in replacement fields
(assert (= f"ey {\"bee
cee\"} dee" "ey bee\ncee dee"))
; Conversion characters and format specifiers
(setv p:9 "other")
(setv !r "bar")
(assert (= f"a{p !r}" "a'xyzzy'"))
(assert (= f"a{p :9}" "axyzzy "))
(assert (= f"a{p:9}" "aother"))
(assert (= f"a{p !r :9}" "a'xyzzy' "))
(assert (= f"a{p !r:9}" "a'xyzzy' "))
(assert (= f"a{p:9 :9}" "aother "))
(assert (= f"a{!r}" "abar"))
(assert (= f"a{!r !r}" "a'bar'"))
; Fun with `r`
(assert (= f"hello {r\"\\n\"}" r"hello \n"))
(assert (= f"hello {r\"\n\"}" "hello \n"))
; The `r` applies too late to avoid interpreting a backslash.
; Braces escaped via doubling
(assert (= f"ab{{cde" "ab{cde"))
(assert (= f"ab{{cde}}}}fg{{{{{{" "ab{cde}}fg{{{"))
(assert (= f"ab{{{(+ 1 1)}}}" "ab{2}"))
; Nested replacement fields
(assert (= f"{2 :{(+ 2 2)}}" " 2"))
(setv value 12.34 width 10 precision 4)
(assert (= f"result: {value :{width}.{precision}}" "result: 12.34"))
; Nested replacement fields with ! and :
(defclass C [object]
(defn __format__ [self format-spec]
(+ "C[" format-spec "]")))
(assert (= f"{(C) : {(str (+ 1 1)) !r :x<5}}" "C[ '2'xx]"))
; Format bracket strings
(assert (= #[f[a{p !r :9}]f] "a'xyzzy' "))
(assert (= #[f-string[result: {value :{width}.{precision}}]f-string]
"result: 12.34")))
(defn test-import-syntax []
"NATIVE: test the import syntax."
@ -1531,9 +1469,24 @@
(assert (= (len "ℵℵℵ♥♥♥\t♥♥\r\n") 11)))
(defn test-keyword-dict-access []
"NATIVE: test keyword dict access"
(assert (= "test" (:foo {:foo "test"}))))
(defn test-keyword-get []
(assert (= (:foo {:foo "test"}) "test"))
(setv f :foo)
(assert (= (f {:foo "test"}) "test"))
(with [(pytest.raises KeyError)] (:foo {:a 1 :b 2}))
(assert (= (:foo {:a 1 :b 2} 3) 3))
(assert (= (:foo {:a 1 :b 2 :foo 5} 3) 5))
(with [(pytest.raises TypeError)] (:foo "Hello World"))
(with [(pytest.raises TypeError)] (:foo (object)))
; The default argument should work regardless of the collection type.
(defclass G [object]
(defn __getitem__ [self k]
(raise KeyError)))
(assert (= (:foo (G) 15) 15)))
(defn test-break-breaking []
@ -1552,16 +1505,6 @@
(assert (= y [5])))
(defn test-empty-list []
"Evaluate an empty list to a []"
(assert (= () [])))
(defn test-string []
(assert (string? (string "a")))
(assert (string? (string 1)))
(assert (= u"unicode" (string "unicode"))))
(defn test-del []
"NATIVE: Test the behavior of del"
(setv foo 42)
@ -1624,21 +1567,12 @@
(defn test-disassemble []
"NATIVE: Test the disassemble function"
(assert (= (disassemble '(do (leaky) (leaky) (macros))) (cond
[PY37 "Module(
(assert (= (disassemble '(do (leaky) (leaky) (macros)))
(.format "Module(
body=[Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[])),
Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[])),
Expr(value=Call(func=Name(id='macros'), args=[], keywords=[]))],
docstring=None)"]
[PY35 "Module(
body=[Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[])),
Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[])),
Expr(value=Call(func=Name(id='macros'), args=[], keywords=[]))])"]
[True "Module(
body=[
Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[], starargs=None, kwargs=None)),
Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[], starargs=None, kwargs=None)),
Expr(value=Call(func=Name(id='macros'), args=[], keywords=[], starargs=None, kwargs=None))])"])))
Expr(value=Call(func=Name(id='macros'), args=[], keywords=[]))]{})"
(if PY38 ",\n type_ignores=[]" ""))))
(assert (= (disassemble '(do (leaky) (leaky) (macros)) True)
"leaky()
leaky()
@ -1676,9 +1610,7 @@ macros()
(defn test-read []
"NATIVE: test that read takes something for stdin and reads"
(if-python2
(import [StringIO [StringIO]])
(import [io [StringIO]]))
(import [io [StringIO]])
(import [hy.models [HyExpression]])
(setv stdin-buffer (StringIO "(+ 2 2)\n(- 2 2)"))
@ -1720,7 +1652,8 @@ macros()
(assert (= (keyword 'foo) :foo))
(assert (= (keyword 'foo-bar) :foo-bar))
(assert (= (keyword 1) :1))
(assert (= (keyword :foo_bar) :foo-bar)))
(setv x :foo_bar)
(assert (= (keyword x) :foo-bar)))
(defn test-name-conversion []
"NATIVE: Test name conversion"
@ -1732,8 +1665,8 @@ macros()
(assert (= (name 'foo_bar) "foo-bar"))
(assert (= (name 1) "1"))
(assert (= (name 1.0) "1.0"))
(assert (= (name :foo) "foo"))
(assert (= (name :foo_bar) "foo-bar"))
(assert (= (name ':foo) "foo"))
(assert (= (name ':foo_bar) "foo-bar"))
(assert (= (name test-name-conversion) "test-name-conversion")))
(defn test-keywords []
@ -1757,21 +1690,20 @@ macros()
(= (identify-keywords 1 "bloo" :foo)
["other" "other" "keyword"])))
#@(pytest.mark.xfail
(defn test-assert-multistatements []
; https://github.com/hylang/hy/issues/1390
(setv s (set))
(setv l [])
(defn f [x]
(.add s x)
(.append l x)
False)
(with [(pytest.raises AssertionError)]
(assert (do (f 1) (f 2)) (do (f 3) (f 4))))
(assert (= s #{1 2 3 4}))))
(assert (= l [1 2 3 4])))
(defn test-underscore_variables []
; https://github.com/hylang/hy/issues/1340
(defclass XYZ []
[_42 6])
(setv _42 6))
(setv x (XYZ))
(assert (= (. x _42) 6)))
@ -1795,3 +1727,206 @@ macros()
"Make sure relative imports work properly"
(import [..resources [tlib]])
(assert (= tlib.SECRET-MESSAGE "Hello World")))
(defn test-exception-cause []
(try (raise ValueError :from NameError)
(except [e [ValueError]]
(assert (= (type (. e __cause__)) NameError)))))
(defn test-kwonly []
"NATIVE: test keyword-only arguments"
;; keyword-only with default works
(defn kwonly-foo-default-false [&kwonly [foo False]] foo)
(assert (= (kwonly-foo-default-false) False))
(assert (= (kwonly-foo-default-false :foo True) True))
;; keyword-only without default ...
(defn kwonly-foo-no-default [&kwonly foo] foo)
(setv attempt-to-omit-default (try
(kwonly-foo-no-default)
(except [e [Exception]] e)))
;; works
(assert (= (kwonly-foo-no-default :foo "quux") "quux"))
;; raises TypeError with appropriate message if not supplied
(assert (isinstance attempt-to-omit-default TypeError))
(assert (in "missing 1 required keyword-only argument: 'foo'"
(. attempt-to-omit-default args [0])))
;; keyword-only with other arg types works
(defn function-of-various-args [a b &rest args &kwonly foo &kwargs kwargs]
(, a b args foo kwargs))
(assert (= (function-of-various-args 1 2 3 4 :foo 5 :bar 6 :quux 7)
(, 1 2 (, 3 4) 5 {"bar" 6 "quux" 7}))))
(defn test-extended-unpacking-1star-lvalues []
(setv [x #*y] [1 2 3 4])
(assert (= x 1))
(assert (= y [2 3 4]))
(setv [a #*b c] "ghijklmno")
(assert (= a "g"))
(assert (= b (list "hijklmn")))
(assert (= c "o")))
(defn test-yield-from []
"NATIVE: testing yield from"
(defn yield-from-test []
(for [i (range 3)]
(yield i))
(yield-from [1 2 3]))
(assert (= (list (yield-from-test)) [0 1 2 1 2 3])))
(defn test-yield-from-exception-handling []
"NATIVE: Ensure exception handling in yield from works right"
(defn yield-from-subgenerator-test []
(yield 1)
(yield 2)
(yield 3)
(assert 0))
(defn yield-from-test []
(for [i (range 3)]
(yield i))
(try
(yield-from (yield-from-subgenerator-test))
(except [e AssertionError]
(yield 4))))
(assert (= (list (yield-from-test)) [0 1 2 1 2 3 4])))
(require [hy.contrib.walk [let]])
(defn test-let-optional []
(let [a 1
b 6
d 2]
(defn foo [&kwonly [a a] b [c d]]
(, a b c))
(assert (= (foo :b "b")
(, 1 "b" 2)))
(assert (= (foo :b 20 :a 10 :c 30)
(, 10 20 30)))))
(defn test-pep-3115 []
(defclass member-table [dict]
(defn --init-- [self]
(setv self.member-names []))
(defn --setitem-- [self key value]
(if (not-in key self)
(.append self.member-names key))
(dict.--setitem-- self key value)))
(defclass OrderedClass [type]
(setv --prepare-- (classmethod (fn [metacls name bases]
(member-table))))
(defn --new-- [cls name bases classdict]
(setv result (type.--new-- cls name bases (dict classdict)))
(setv result.member-names classdict.member-names)
result))
(defclass MyClass [:metaclass OrderedClass]
(defn method1 [self] (pass))
(defn method2 [self] (pass)))
(assert (= (. (MyClass) member-names)
["__module__" "__qualname__" "method1" "method2"])))
(import [asyncio [get-event-loop sleep]])
(defn test-unpacking-pep448-1star []
(setv l [1 2 3])
(setv p [4 5])
(assert (= ["a" #*l "b" #*p #*l] ["a" 1 2 3 "b" 4 5 1 2 3]))
(assert (= (, "a" #*l "b" #*p #*l) (, "a" 1 2 3 "b" 4 5 1 2 3)))
(assert (= #{"a" #*l "b" #*p #*l} #{"a" "b" 1 2 3 4 5}))
(defn f [&rest args] args)
(assert (= (f "a" #*l "b" #*p #*l) (, "a" 1 2 3 "b" 4 5 1 2 3)))
(assert (= (+ #*l #*p) 15))
(assert (= (and #*l) 3)))
(defn test-unpacking-pep448-2star []
(setv d1 {"a" 1 "b" 2})
(setv d2 {"c" 3 "d" 4})
(assert (= {1 "x" #**d1 #**d2 2 "y"} {"a" 1 "b" 2 "c" 3 "d" 4 1 "x" 2 "y"}))
(defn fun [&optional a b c d e f] [a b c d e f])
(assert (= (fun #**d1 :e "eee" #**d2) [1 2 3 4 "eee" None])))
(defn run-coroutine [coro]
"Run a coroutine until its done in the default event loop."""
(.run_until_complete (get-event-loop) (coro)))
(defn test-fn/a []
(assert (= (run-coroutine (fn/a [] (await (sleep 0)) [1 2 3]))
[1 2 3])))
(defn test-defn/a []
(defn/a coro-test []
(await (sleep 0))
[1 2 3])
(assert (= (run-coroutine coro-test) [1 2 3])))
(defn test-decorated-defn/a []
(defn decorator [func] (fn/a [] (/ (await (func)) 2)))
#@(decorator
(defn/a coro-test []
(await (sleep 0))
42))
(assert (= (run-coroutine coro-test) 21)))
(defclass AsyncWithTest []
(defn --init-- [self val]
(setv self.val val)
None)
(defn/a --aenter-- [self]
self.val)
(defn/a --aexit-- [self tyle value traceback]
(setv self.val None)))
(defn test-single-with/a []
(run-coroutine
(fn/a []
(with/a [t (AsyncWithTest 1)]
(assert (= t 1))))))
(defn test-two-with/a []
(run-coroutine
(fn/a []
(with/a [t1 (AsyncWithTest 1)
t2 (AsyncWithTest 2)]
(assert (= t1 1))
(assert (= t2 2))))))
(defn test-thrice-with/a []
(run-coroutine
(fn/a []
(with/a [t1 (AsyncWithTest 1)
t2 (AsyncWithTest 2)
t3 (AsyncWithTest 3)]
(assert (= t1 1))
(assert (= t2 2))
(assert (= t3 3))))))
(defn test-quince-with/a []
(run-coroutine
(fn/a []
(with/a [t1 (AsyncWithTest 1)
t2 (AsyncWithTest 2)
t3 (AsyncWithTest 3)
_ (AsyncWithTest 4)]
(assert (= t1 1))
(assert (= t2 2))
(assert (= t3 3))))))

View File

@ -1,11 +1,8 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import [hy._compat [PY3]])
(defn test-hyphen []
(setv a-b 1)
(assert (= a-b 1))
@ -63,9 +60,7 @@
(defn test-higher-unicode []
(setv 😂 "emoji")
(assert (= 😂 "emoji"))
(if PY3
(assert (= hyx_Xface_with_tears_of_joyX "emoji"))
(assert (= hyx_XU1f602X "emoji"))))
(assert (= hyx_Xface_with_tears_of_joyX "emoji")))
(defn test-nameless-unicode []
@ -157,3 +152,28 @@
["⚘-⚘" "hyx_XflowerX_XflowerX"]]]
(assert (= (mangle a) b))
(assert (= (unmangle b) a))))
(defn test-nongraphic []
; https://github.com/hylang/hy/issues/1694
(assert (= (mangle " ") "hyx_XspaceX"))
(assert (= (mangle "\a") "hyx_XU7X"))
(assert (= (mangle "\t") "hyx_XU9X"))
(assert (= (mangle "\n") "hyx_XUaX"))
(assert (= (mangle "\r") "hyx_XUdX"))
(assert (= (mangle "\r") "hyx_XUdX"))
(setv c (try unichr (except [NameError] chr)))
(assert (= (mangle (c 127)) "hyx_XU7fX"))
(assert (= (mangle (c 128)) "hyx_XU80X"))
(assert (= (mangle (c 0xa0)) "hyx_XnoHbreak_spaceX"))
(assert (= (mangle (c 0x378)) "hyx_XU378X"))
(assert (= (mangle (c 0x200a) "hyx_Xhair_spaceX")))
(assert (= (mangle (c 0x2065)) "hyx_XU2065X"))
(assert (= (mangle (c 0x1000c)) "hyx_XU1000cX")))
(defn test-mangle-bad-indent []
; Shouldn't crash with IndentationError
(mangle " 0\n 0"))

View File

@ -1,210 +0,0 @@
;; Copyright 2018 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import [hy._compat [PY35]])
(setv square (fn [x]
(* x x)))
(setv test_basic_math (fn []
"NATIVE: Test basic math."
(assert (= (+ 2 2) 4))))
(setv test_mult (fn []
"NATIVE: Test multiplication."
(assert (= 4 (square 2)))
(assert (= 8 (* 8)))
(assert (= 1 (*)))))
(setv test_sub (fn []
"NATIVE: Test subtraction"
(assert (= 4 (- 8 4)))
(assert (= -8 (- 8)))))
(setv test_add (fn []
"NATIVE: Test addition"
(assert (= 4 (+ 1 1 1 1)))
(assert (= 8 (+ 8)))
(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)))
; Commented out until float constants get implemented
; (assert (= 0.5 (/ 1 2)))
(assert (= 1 (* 2 (/ 1 2))))))
(setv test_int_div (fn []
"NATIVE: Test integer division"
(assert (= 25 (// 101 2 2)))))
(defn test-modulo []
"NATIVE: test mod"
(assert (= (% 10 2) 0)))
(defn test-pow []
"NATIVE: test pow"
(assert (= (** 10 2) 100)))
(defn test-lshift []
"NATIVE: test lshift"
(assert (= (<< 1 2) 4)))
(defn test-rshift []
"NATIVE: test lshift"
(assert (= (>> 8 1) 4)))
(defn test-bitor []
"NATIVE: test lshift"
(assert (= (| 1 2) 3)))
(defn test-bitxor []
"NATIVE: test xor"
(assert (= (^ 1 2) 3)))
(defn test-bitand []
"NATIVE: test lshift"
(assert (= (& 1 2) 0)))
(defn test-augassign-add []
"NATIVE: test augassign add"
(setv x 1)
(+= x 41)
(assert (= x 42)))
(defn test-augassign-sub []
"NATIVE: test augassign sub"
(setv x 1)
(-= x 41)
(assert (= x -40)))
(defn test-augassign-mult []
"NATIVE: test augassign mult"
(setv x 1)
(*= x 41)
(assert (= x 41)))
(defn test-augassign-div []
"NATIVE: test augassign div"
(setv x 42)
(/= x 2)
(assert (= x 21)))
(defn test-augassign-floordiv []
"NATIVE: test augassign floordiv"
(setv x 42)
(//= x 2)
(assert (= x 21)))
(defn test-augassign-mod []
"NATIVE: test augassign mod"
(setv x 42)
(%= x 2)
(assert (= x 0)))
(defn test-augassign-pow []
"NATIVE: test augassign pow"
(setv x 2)
(**= x 3)
(assert (= x 8)))
(defn test-augassign-lshift []
"NATIVE: test augassign lshift"
(setv x 2)
(<<= x 2)
(assert (= x 8)))
(defn test-augassign-rshift []
"NATIVE: test augassign rshift"
(setv x 8)
(>>= x 1)
(assert (= x 4)))
(defn test-augassign-bitand []
"NATIVE: test augassign bitand"
(setv x 8)
(&= x 1)
(assert (= x 0)))
(defn test-augassign-bitor []
"NATIVE: test augassign bitand"
(setv x 0)
(|= x 2)
(assert (= x 2)))
(defn test-augassign-bitxor []
"NATIVE: test augassign bitand"
(setv x 1)
(^= x 1)
(assert (= x 0)))
(defn overflow-int-to-long []
"NATIVE: test if int does not raise an overflow exception"
(assert (integer? (+ 1 1000000000000000000000000))))
(defclass HyTestMatrix [list]
[--matmul--
(fn [self other]
(setv n (len self)
m (len (. other [0]))
result [])
(for [i (range m)]
(setv result-row [])
(for [j (range n)]
(setv dot-product 0)
(for [k (range (len (. self [0])))]
(+= dot-product (* (. self [i] [k])
(. other [k] [j]))))
(.append result-row dot-product))
(.append result result-row))
result)])
(setv first-test-matrix (HyTestMatrix [[1 2 3]
[4 5 6]
[7 8 9]]))
(setv second-test-matrix (HyTestMatrix [[2 0 0]
[0 2 0]
[0 0 2]]))
(setv product-of-test-matrices (HyTestMatrix [[ 2 4 6]
[ 8 10 12]
[14 16 18]]))
(defn test-matmul []
"NATIVE: test matrix multiplication"
(if PY35
(assert (= (@ first-test-matrix second-test-matrix)
product-of-test-matrices))
;; Python <= 3.4
(do
(setv matmul-attempt (try (@ first-test-matrix second-test-matrix)
(except [e [Exception]] e)))
(assert (isinstance matmul-attempt NameError)))))
(defn test-augassign-matmul []
"NATIVE: test augmented-assignment matrix multiplication"
(setv matrix first-test-matrix
matmul-attempt (try (@= matrix second-test-matrix)
(except [e [Exception]] e)))
(if PY35
(assert (= product-of-test-matrices matrix))
(assert (isinstance matmul-attempt NameError))))

View File

@ -0,0 +1,70 @@
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(defmacro do-until [&rest args]
(import
[hy.model-patterns [whole FORM notpexpr dolike]]
[funcparserlib.parser [many]])
(setv [body condition] (->> args (.parse (whole
[(many (notpexpr "until")) (dolike "until")]))))
(setv g (gensym))
`(do
(setv ~g True)
(while (or ~g (not (do ~@condition)))
~@body
(setv ~g False))))
(defn test-do-until []
(setv n 0 s "")
(do-until
(+= s "x")
(until (+= n 1) (>= n 3)))
(assert (= s "xxx"))
(do-until
(+= s "x")
(until (+= n 1) (>= n 3)))
(assert (= s "xxxx")))
(defmacro loop [&rest args]
(import
[hy.model-patterns [whole FORM sym SYM]]
[funcparserlib.parser [many]])
(setv [loopers body] (->> args (.parse (whole [
(many (|
(>> (+ (sym "while") FORM) (fn [x] [x]))
(+ (sym "for") SYM (sym "in") FORM)
(+ (sym "for") SYM (sym "from") FORM (sym "to") FORM)))
(sym "do")
(many FORM)]))))
(defn f [loopers]
(setv [head tail] [(first loopers) (cut loopers 1)])
(print head)
(cond
[(none? head)
`(do ~@body)]
[(= (len head) 1)
`(while ~@head ~(f tail))]
[(= (len head) 2)
`(for [~@head] ~(f tail))]
[True ; (= (len head) 3)
(setv [sym from to] head)
`(for [~sym (range ~from (inc ~to))] ~(f tail))]))
(f loopers))
(defn test-loop []
(setv l [])
(loop
for x in "abc"
do (.append l x))
(assert (= l ["a" "b" "c"]))
(setv l [] k 2)
(loop
while (> k 0)
for n from 1 to 3
for p in [k n (* 10 n)]
do (.append l p) (-= k 1))
(print l)
(assert (= l [2 1 10 -1 2 20 -4 3 30])))

View File

@ -1,8 +1,9 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import [hy.errors [HyTypeError]])
(import pytest
[hy.errors [HyTypeError HyMacroExpansionError]])
(defmacro rev [&rest body]
"Execute the `body` statements in reverse"
@ -65,13 +66,13 @@
(try
(eval '(defmacro f [&kwonly a b]))
(except [e HyTypeError]
(assert (= e.message "macros cannot use &kwonly")))
(assert (= e.msg "macros cannot use &kwonly")))
(else (assert False)))
(try
(eval '(defmacro f [&kwargs kw]))
(except [e HyTypeError]
(assert (= e.message "macros cannot use &kwargs")))
(assert (= e.msg "macros cannot use &kwargs")))
(else (assert False))))
(defn test-fn-calling-macro []
@ -101,7 +102,7 @@
(defn test-midtree-yield-in-for []
"NATIVE: test yielding in a for with a return"
(defn kruft-in-for []
(for* [i (range 5)]
(for [i (range 5)]
(yield i))
(+ 1 2)))
@ -117,7 +118,7 @@
(defn test-multi-yield []
"NATIVE: testing multiple yields"
(defn multi-yield []
(for* [i (range 3)]
(for [i (range 3)]
(yield i))
(yield "a")
(yield "end"))
@ -139,15 +140,11 @@
(assert initialized)
(assert (test-initialized))
(defn test-if-python2 []
(import sys)
(assert (= (get sys.version_info 0)
(if-python2 2 3))))
(defn test-gensym-in-macros []
(import ast)
(import [astor.code-gen [to-source]])
(import [hy.importer [import_buffer_to_ast]])
(import [hy.compiler [hy-compile]])
(import [hy.lex [hy-parse]])
(setv macro1 "(defmacro nif [expr pos zero neg]
(setv g (gensym))
`(do
@ -160,20 +157,21 @@
")
;; expand the macro twice, should use a different
;; gensym each time
(setv _ast1 (import_buffer_to_ast macro1 "foo"))
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
(setv _ast1 (hy-compile (hy-parse macro1) __name__))
(setv _ast2 (hy-compile (hy-parse macro1) __name__))
(setv s1 (to_source _ast1))
(setv s2 (to_source _ast2))
;; and make sure there is something new that starts with _;G|
(assert (in (mangle "_;G|") s1))
(assert (in (mangle "_;G|") s2))
;; and make sure there is something new that starts with _G\uffff
(assert (in (mangle "_G\uffff") s1))
(assert (in (mangle "_G\uffff") s2))
;; but make sure the two don't match each other
(assert (not (= s1 s2))))
(defn test-with-gensym []
(import ast)
(import [astor.code-gen [to-source]])
(import [hy.importer [import_buffer_to_ast]])
(import [hy.compiler [hy-compile]])
(import [hy.lex [hy-parse]])
(setv macro1 "(defmacro nif [expr pos zero neg]
(with-gensyms [a]
`(do
@ -186,18 +184,19 @@
")
;; expand the macro twice, should use a different
;; gensym each time
(setv _ast1 (import_buffer_to_ast macro1 "foo"))
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
(setv _ast1 (hy-compile (hy-parse macro1) __name__))
(setv _ast2 (hy-compile (hy-parse macro1) __name__))
(setv s1 (to_source _ast1))
(setv s2 (to_source _ast2))
(assert (in (mangle "_;a|") s1))
(assert (in (mangle "_;a|") s2))
(assert (in (mangle "_a\uffff") s1))
(assert (in (mangle "_a\uffff") s2))
(assert (not (= s1 s2))))
(defn test-defmacro-g! []
(defn test-defmacro/g! []
(import ast)
(import [astor.code-gen [to-source]])
(import [hy.importer [import_buffer_to_ast]])
(import [hy.compiler [hy-compile]])
(import [hy.lex [hy-parse]])
(setv macro1 "(defmacro/g! nif [expr pos zero neg]
`(do
(setv ~g!res ~expr)
@ -209,24 +208,25 @@
")
;; expand the macro twice, should use a different
;; gensym each time
(setv _ast1 (import_buffer_to_ast macro1 "foo"))
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
(setv _ast1 (hy-compile (hy-parse macro1) __name__))
(setv _ast2 (hy-compile (hy-parse macro1) __name__))
(setv s1 (to_source _ast1))
(setv s2 (to_source _ast2))
(assert (in "_;res|" s1))
(assert (in "_;res|" s2))
(assert (in (mangle "_res\uffff") s1))
(assert (in (mangle "_res\uffff") s2))
(assert (not (= s1 s2)))
;; defmacro/g! didn't like numbers initially because they
;; don't have a startswith method and blew up during expansion
(setv macro2 "(defmacro/g! two-point-zero [] `(+ (float 1) 1.0))")
(assert (import_buffer_to_ast macro2 "foo")))
(assert (hy-compile (hy-parse macro2) __name__)))
(defn test-defmacro! []
;; defmacro! must do everything defmacro/g! can
(import ast)
(import [astor.code-gen [to-source]])
(import [hy.importer [import_buffer_to_ast]])
(import [hy.compiler [hy-compile]])
(import [hy.lex [hy-parse]])
(setv macro1 "(defmacro! nif [expr pos zero neg]
`(do
(setv ~g!res ~expr)
@ -238,18 +238,18 @@
")
;; expand the macro twice, should use a different
;; gensym each time
(setv _ast1 (import_buffer_to_ast macro1 "foo"))
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
(setv _ast1 (hy-compile (hy-parse macro1) __name__))
(setv _ast2 (hy-compile (hy-parse macro1) __name__))
(setv s1 (to_source _ast1))
(setv s2 (to_source _ast2))
(assert (in "_;res|" s1))
(assert (in "_;res|" s2))
(assert (in (mangle "_res\uffff") s1))
(assert (in (mangle "_res\uffff") s2))
(assert (not (= s1 s2)))
;; defmacro/g! didn't like numbers initially because they
;; don't have a startswith method and blew up during expansion
(setv macro2 "(defmacro! two-point-zero [] `(+ (float 1) 1.0))")
(assert (import_buffer_to_ast macro2 "foo"))
(assert (hy-compile (hy-parse macro2) __name__))
(defmacro! foo! [o!foo] `(do ~g!foo ~g!foo))
;; test that o! becomes g!
@ -257,7 +257,14 @@
;; test that o! is evaluated once only
(setv foo 40)
(foo! (+= foo 1))
(assert (= 41 foo)))
(assert (= 41 foo))
;; test &optional args
(defmacro! bar! [o!a &optional [o!b 1]] `(do ~g!a ~g!a ~g!b ~g!b))
;; test that o!s are evaluated once only
(bar! (+= foo 1) (+= foo 1))
(assert (= 43 foo))
;; test that the optional arg works
(assert (= (bar! 2) 1)))
(defn test-if-not []
@ -313,12 +320,195 @@
(global --name--)
(setv oldname --name--)
(setv --name-- "__main__")
(defn main []
(print 'Hy)
42)
(defn main [x]
(print (integer? x))
x)
(try
(defmain [&rest args]
(main))
(main 42))
(assert False)
(except [e SystemExit]
(assert (= (str e) "42"))))
;; Try a `defmain` without args
(try
(defmain []
(main 42))
(assert False)
(except [e SystemExit]
(assert (= (str e) "42"))))
;; Try a `defmain` with only one arg
(import sys)
(setv oldargv sys.argv)
(try
(setv sys.argv [1])
(defmain [x]
(main x))
(assert False)
(except [e SystemExit]
(assert (= (str e) "1"))))
(setv sys.argv oldargv)
(setv --name-- oldname))
(defn test-macro-namespace-resolution []
"Confirm that local versions of macro-macro dependencies do not shadow the
versions from the macro's own module, but do resolve unbound macro references
in expansions."
;; `nonlocal-test-macro` is a macro used within
;; `tests.resources.macro-with-require.test-module-macro`.
;; Here, we introduce an equivalently named version in local scope that, when
;; used, will expand to a different output string.
(defmacro nonlocal-test-macro [x]
(print "this is the local version of `nonlocal-test-macro`!"))
;; Was the above macro created properly?
(assert (in "nonlocal_test_macro" __macros__))
(setv nonlocal-test-macro (get __macros__ "nonlocal_test_macro"))
(require [tests.resources.macro-with-require [*]])
(setv module-name-var "tests.native_tests.native_macros.test-macro-namespace-resolution")
(assert (= (+ "This macro was created in tests.resources.macros, "
"expanded in tests.native_tests.native_macros.test-macro-namespace-resolution "
"and passed the value 2.")
(test-module-macro 2)))
(assert (= (+ "This macro was created in tests.resources.macros, "
"expanded in tests.native_tests.native_macros.test-macro-namespace-resolution "
"and passed the value 2.")
#test-module-tag 2))
;; Now, let's use a `require`d macro that depends on another macro defined only
;; in this scope.
(defmacro local-test-macro [x]
(.format "This is the local version of `nonlocal-test-macro` returning {}!" (int x)))
(assert (= "This is the local version of `nonlocal-test-macro` returning 3!"
(test-module-macro-2 3)))
(assert (= "This is the local version of `nonlocal-test-macro` returning 3!"
#test-module-tag-2 3)))
(defn test-macro-from-module []
"Macros loaded from an external module, which itself `require`s macros, should
work without having to `require` the module's macro dependencies (due to
[minimal] macro namespace resolution).
In doing so we also confirm that a module's `__macros__` attribute is correctly
loaded and used.
Additionally, we confirm that `require` statements are executed via loaded bytecode."
(import os sys marshal types)
(import importlib)
(setv pyc-file (importlib.util.cache-from-source
(os.path.realpath
(os.path.join
"tests" "resources" "macro_with_require.hy"))))
;; Remove any cached byte-code, so that this runs from source and
;; gets evaluated in this module.
(when (os.path.isfile pyc-file)
(os.unlink pyc-file)
(.clear sys.path_importer_cache)
(when (in "tests.resources.macro_with_require" sys.modules)
(del (get sys.modules "tests.resources.macro_with_require"))
(__macros__.clear)
(__tags__.clear)))
;; Ensure that bytecode isn't present when we require this module.
(assert (not (os.path.isfile pyc-file)))
(defn test-requires-and-macros []
(require [tests.resources.macro-with-require
[test-module-macro]])
;; Make sure that `require` didn't add any of its `require`s
(assert (not (in "nonlocal-test-macro" __macros__)))
;; and that it didn't add its tags.
(assert (not (in "test_module_tag" __tags__)))
;; Now, require everything.
(require [tests.resources.macro-with-require [*]])
;; Again, make sure it didn't add its required macros and/or tags.
(assert (not (in "nonlocal-test-macro" __macros__)))
;; Its tag(s) should be here now.
(assert (in "test_module_tag" __tags__))
;; The test macro expands to include this symbol.
(setv module-name-var "tests.native_tests.native_macros")
(assert (= (+ "This macro was created in tests.resources.macros, "
"expanded in tests.native_tests.native_macros "
"and passed the value 1.")
(test-module-macro 1)))
(assert (= (+ "This macro was created in tests.resources.macros, "
"expanded in tests.native_tests.native_macros "
"and passed the value 1.")
#test-module-tag 1)))
(test-requires-and-macros)
;; Now that bytecode is present, reload the module, clear the `require`d
;; macros and tags, and rerun the tests.
(assert (os.path.isfile pyc-file))
;; Reload the module and clear the local macro context.
(.clear sys.path_importer_cache)
(del (get sys.modules "tests.resources.macro_with_require"))
(.clear __macros__)
(.clear __tags__)
;; XXX: There doesn't seem to be a way--via standard import mechanisms--to
;; ensure that an imported module used the cached bytecode. We'll simply have
;; to trust that the .pyc loading convention was followed.
(test-requires-and-macros))
(defn test-recursive-require-star []
"(require [foo [*]]) should pull in macros required by `foo`."
(require [tests.resources.macro-with-require [*]])
(test-macro)
(assert (= blah 1)))
(defn test-macro-errors []
(import traceback
[hy.importer [hy-parse]])
(setv test-expr (hy-parse "(defmacro blah [x] `(print ~@z)) (blah y)"))
(with [excinfo (pytest.raises HyMacroExpansionError)]
(eval test-expr))
(setv output (traceback.format_exception_only
excinfo.type excinfo.value))
(setv output (cut (.splitlines (.strip (first output))) 1))
(setv expected [" File \"<string>\", line 1"
" (defmacro blah [x] `(print ~@z)) (blah y)"
" ^------^"
"expanding macro blah"
" NameError: global name 'z' is not defined"])
(assert (= (cut expected 0 -1) (cut output 0 -1)))
(assert (or (= (get expected -1) (get output -1))
;; Handle PyPy's peculiarities
(= (.replace (get expected -1) "global " "") (get output -1))))
;; This should throw a `HyWrapperError` that gets turned into a
;; `HyMacroExpansionError`.
(with [excinfo (pytest.raises HyMacroExpansionError)]
(eval '(do (defmacro wrap-error-test []
(fn []))
(wrap-error-test))))
(assert (in "HyWrapperError" (str excinfo.value))))

View File

@ -1,9 +1,7 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import pytest [hy._compat [PY35]])
(defmacro op-and-shadow-test [op &rest body]
; Creates two tests with the given `body`, one where all occurrences
; of the symbol `f` are syntactically replaced with `op` (a test of
@ -28,7 +26,7 @@
(defmacro forbid [expr]
`(assert (try
(eval '~expr)
(except [TypeError] True)
(except [[TypeError SyntaxError]] True)
(else (raise AssertionError)))))
@ -36,7 +34,7 @@
(assert (= (f) 0))
(defclass C [object] [__pos__ (fn [self] "called __pos__")])
(defclass C [object] (defn __pos__ [self] "called __pos__"))
(assert (= (f (C)) "called __pos__"))
(assert (= (f 1 2) 3))
@ -102,14 +100,14 @@
(forbid (f 1 2 3)))
(when PY35 (op-and-shadow-test @
(defclass C [object] [
__init__ (fn [self content] (setv self.content content))
__matmul__ (fn [self other] (C (+ self.content other.content)))])
(op-and-shadow-test @
(defclass C [object]
(defn __init__ [self content] (setv self.content content))
(defn __matmul__ [self other] (C (+ self.content other.content))))
(forbid (f))
(assert (do (setv c (C "a")) (is (f c) c)))
(assert (= (. (f (C "b") (C "c")) content) "bc"))
(assert (= (. (f (C "d") (C "e") (C "f")) content) "def"))))
(assert (= (. (f (C "d") (C "e") (C "f")) content) "def")))
(op-and-shadow-test <<
@ -173,11 +171,11 @@
; Make sure chained comparisons use `and`, not `&`.
; https://github.com/hylang/hy/issues/1191
(defclass C [object] [
__init__ (fn [self x]
(defclass C [object]
(defn __init__ [self x]
(setv self.x x))
__lt__ (fn [self other]
self.x)])
(defn __lt__ [self other]
self.x))
(assert (= (f (C "a") (C "b") (C "c")) "b")))
@ -305,3 +303,43 @@
(assert (= (f "hello" 1) "e"))
(assert (= (f [[1 2 3] [4 5 6] [7 8 9]] 1 2) 6))
(assert (= (f {"x" {"y" {"z" 12}}} "x" "y" "z") 12)))
(defn test-augassign []
(setv b 2 c 3 d 4)
(defmacro same-as [expr1 expr2 expected-value]
`(do
(setv a 4)
~expr1
(setv expr1-value a)
(setv a 4)
~expr2
(assert (= expr1-value a ~expected-value))))
(same-as (+= a b c d) (+= a (+ b c d)) 13)
(same-as (-= a b c d) (-= a (+ b c d)) -5)
(same-as (*= a b c d) (*= a (* b c d)) 96)
(same-as (**= a b c) (**= a (** b c)) 65,536)
(same-as (/= a b c d) (/= a (* b c d)) (/ 1 6))
(same-as (//= a b c d) (//= a (* b c d)) 0)
(same-as (<<= a b c d) (<<= a (+ b c d)) 0b10_00000_00000)
(same-as (>>= a b c d) (>>= a (+ b c d)) 0)
(same-as (&= a b c d) (&= a (& b c d)) 0)
(same-as (|= a b c d) (|= a (| b c d)) 0b111)
(defclass C [object]
(defn __init__ [self content] (setv self.content content))
(defn __matmul__ [self other] (C (+ self.content other.content))))
(setv a (C "a") b (C "b") c (C "c") d (C "d"))
(@= a b c d)
(assert (= a.content "abcd"))
(setv a (C "a"))
(@= a (@ b c d))
(assert (= a.content "abcd"))
(setv a 15)
(%= a 9)
(assert (= a 6))
(setv a 0b1100)
(^= a 0b1010)
(assert (= a 0b0110)))

View File

@ -1,103 +0,0 @@
;; Copyright 2018 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
;; Tests where the emitted code relies on Python ≥3.5.
;; conftest.py skips this file when running on Python <3.5.
(import [asyncio [get-event-loop sleep]])
(defn test-unpacking-pep448-1star []
(setv l [1 2 3])
(setv p [4 5])
(assert (= ["a" #*l "b" #*p #*l] ["a" 1 2 3 "b" 4 5 1 2 3]))
(assert (= (, "a" #*l "b" #*p #*l) (, "a" 1 2 3 "b" 4 5 1 2 3)))
(assert (= #{"a" #*l "b" #*p #*l} #{"a" "b" 1 2 3 4 5}))
(defn f [&rest args] args)
(assert (= (f "a" #*l "b" #*p #*l) (, "a" 1 2 3 "b" 4 5 1 2 3)))
(assert (= (+ #*l #*p) 15))
(assert (= (and #*l) 3)))
(defn test-unpacking-pep448-2star []
(setv d1 {"a" 1 "b" 2})
(setv d2 {"c" 3 "d" 4})
(assert (= {1 "x" #**d1 #**d2 2 "y"} {"a" 1 "b" 2 "c" 3 "d" 4 1 "x" 2 "y"}))
(defn fun [&optional a b c d e f] [a b c d e f])
(assert (= (fun #**d1 :e "eee" #**d2) [1 2 3 4 "eee" None])))
(defn run-coroutine [coro]
"Run a coroutine until its done in the default event loop."""
(.run_until_complete (get-event-loop) (coro)))
(defn test-fn/a []
(assert (= (run-coroutine (fn/a [] (await (sleep 0)) [1 2 3]))
[1 2 3])))
(defn test-defn/a []
(defn/a coro-test []
(await (sleep 0))
[1 2 3])
(assert (= (run-coroutine coro-test) [1 2 3])))
(defn test-decorated-defn/a []
(defn decorator [func] (fn/a [] (/ (await (func)) 2)))
#@(decorator
(defn/a coro-test []
(await (sleep 0))
42))
(assert (= (run-coroutine coro-test) 21)))
(defclass AsyncWithTest []
(defn --init-- [self val]
(setv self.val val)
None)
(defn/a --aenter-- [self]
self.val)
(defn/a --aexit-- [self tyle value traceback]
(setv self.val None)))
(defn test-single-with/a []
(run-coroutine
(fn/a []
(with/a [t (AsyncWithTest 1)]
(assert (= t 1))))))
(defn test-two-with/a []
(run-coroutine
(fn/a []
(with/a [t1 (AsyncWithTest 1)
t2 (AsyncWithTest 2)]
(assert (= t1 1))
(assert (= t2 2))))))
(defn test-thrice-with/a []
(run-coroutine
(fn/a []
(with/a [t1 (AsyncWithTest 1)
t2 (AsyncWithTest 2)
t3 (AsyncWithTest 3)]
(assert (= t1 1))
(assert (= t2 2))
(assert (= t3 3))))))
(defn test-quince-with/a []
(run-coroutine
(fn/a []
(with/a [t1 (AsyncWithTest 1)
t2 (AsyncWithTest 2)
t3 (AsyncWithTest 3)
_ (AsyncWithTest 4)]
(assert (= t1 1))
(assert (= t2 2))
(assert (= t3 3))))))

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -6,6 +6,7 @@
;; conftest.py skips this file when running on Python <3.6.
(import [asyncio [get-event-loop sleep]])
(import [typing [get-type-hints List Dict]])
(defn run-coroutine [coro]
@ -13,7 +14,7 @@
(.run_until_complete (get-event-loop) (coro)))
(defn test-for/a []
(defn test-for-async []
(defn/a numbers []
(for [i [1 2]]
(yield i)))
@ -21,11 +22,11 @@
(run-coroutine
(fn/a []
(setv x 0)
(for/a [a (numbers)]
(for [:async a (numbers)]
(setv x (+ x a)))
(assert (= x 3)))))
(defn test-for/a-else []
(defn test-for-async-else []
(defn/a numbers []
(for [i [1 2]]
(yield i)))
@ -33,15 +34,29 @@
(run-coroutine
(fn/a []
(setv x 0)
(for/a [a (numbers)]
(for [:async a (numbers)]
(setv x (+ x a))
(else (setv x (+ x 50))))
(assert (= x 53)))))
(defn test-variable-annotations []
(defclass AnnotationContainer []
(setv ^int x 1 y 2)
(^bool z))
(setv annotations (get-type-hints AnnotationContainer))
(assert (= (get annotations "x") int))
(assert (= (get annotations "z") bool)))
(defn test-of []
(assert (= (of str) str))
(assert (= (of List int) (get List int)))
(assert (= (of Dict str str) (get Dict (, str str)))))
(defn test-pep-487 []
(defclass QuestBase []
[--init-subclass-- (fn [cls swallow &kwargs kwargs]
(setv cls.swallow swallow))])
(defn --init-subclass-- [cls swallow &kwargs kwargs]
(setv cls.swallow swallow)))
(defclass Quest [QuestBase :swallow "african"])
(assert (= (. (Quest) swallow) "african")))

View File

@ -0,0 +1,29 @@
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
;; Tests where the emitted code relies on Python ≥3.8.
;; conftest.py skips this file when running on Python <3.8.
(import pytest)
(defn test-setx []
(setx y (+ (setx x (+ "a" "b")) "c"))
(assert (= x "ab"))
(assert (= y "abc"))
(setv l [])
(for [x [1 2 3]]
(when (>= (setx y (+ x 8)) 10)
(.append l y)))
(assert (= l [10 11]))
(setv a ["apple" None "banana"])
(setv filtered (lfor
i (range (len a))
:if (is-not (setx v (get a i)) None)
v))
(assert (= filtered ["apple" "banana"]))
(assert (= v "banana"))
(with [(pytest.raises NameError)]
i))

View File

@ -1,109 +0,0 @@
;; Copyright 2018 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
;; Tests where the emitted code relies on Python 3.
;; conftest.py skips this file when running on Python 2.
(defn test-exception-cause []
(try (raise ValueError :from NameError)
(except [e [ValueError]]
(assert (= (type (. e __cause__)) NameError)))))
(defn test-kwonly []
"NATIVE: test keyword-only arguments"
;; keyword-only with default works
(defn kwonly-foo-default-false [&kwonly [foo False]] foo)
(assert (= (kwonly-foo-default-false) False))
(assert (= (kwonly-foo-default-false :foo True) True))
;; keyword-only without default ...
(defn kwonly-foo-no-default [&kwonly foo] foo)
(setv attempt-to-omit-default (try
(kwonly-foo-no-default)
(except [e [Exception]] e)))
;; works
(assert (= (kwonly-foo-no-default :foo "quux") "quux"))
;; raises TypeError with appropriate message if not supplied
(assert (isinstance attempt-to-omit-default TypeError))
(assert (in "missing 1 required keyword-only argument: 'foo'"
(. attempt-to-omit-default args [0])))
;; keyword-only with other arg types works
(defn function-of-various-args [a b &rest args &kwonly foo &kwargs kwargs]
(, a b args foo kwargs))
(assert (= (function-of-various-args 1 2 3 4 :foo 5 :bar 6 :quux 7)
(, 1 2 (, 3 4) 5 {"bar" 6 "quux" 7}))))
(defn test-extended-unpacking-1star-lvalues []
(setv [x #*y] [1 2 3 4])
(assert (= x 1))
(assert (= y [2 3 4]))
(setv [a #*b c] "ghijklmno")
(assert (= a "g"))
(assert (= b (list "hijklmn")))
(assert (= c "o")))
(defn test-yield-from []
"NATIVE: testing yield from"
(defn yield-from-test []
(for* [i (range 3)]
(yield i))
(yield-from [1 2 3]))
(assert (= (list (yield-from-test)) [0 1 2 1 2 3])))
(defn test-yield-from-exception-handling []
"NATIVE: Ensure exception handling in yield from works right"
(defn yield-from-subgenerator-test []
(yield 1)
(yield 2)
(yield 3)
(assert 0))
(defn yield-from-test []
(for* [i (range 3)]
(yield i))
(try
(yield-from (yield-from-subgenerator-test))
(except [e AssertionError]
(yield 4))))
(assert (= (list (yield-from-test)) [0 1 2 1 2 3 4])))
(require [hy.contrib.walk [let]])
(defn test-let-optional []
(let [a 1
b 6
d 2]
(defn foo [&kwonly [a a] b [c d]]
(, a b c))
(assert (= (foo :b "b")
(, 1 "b" 2)))
(assert (= (foo :b 20 :a 10 :c 30)
(, 10 20 30)))))
(defn test-pep-3115 []
(defclass member-table [dict]
[--init-- (fn [self] (setv self.member-names []))
--setitem-- (fn [self key value]
(if (not-in key self)
(.append self.member-names key))
(dict.--setitem-- self key value))])
(defclass OrderedClass [type]
[--prepare-- (classmethod (fn [metacls name bases] (member-table)))
--new-- (fn [cls name bases classdict]
(setv result (type.--new-- cls name bases (dict classdict)))
(setv result.member-names classdict.member-names)
result)])
(defclass MyClass [:metaclass OrderedClass]
[method1 (fn [self] (pass))
method2 (fn [self] (pass))])
(assert (= (. (MyClass) member-names)
["__module__" "__qualname__" "method1" "method2"])))

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -9,7 +9,7 @@
"NATIVE: test for quoting functionality"
(setv q (quote (a b c)))
(assert (= (len q) 3))
(assert (= q [(quote a) (quote b) (quote c)])))
(assert (= q (HyExpression [(quote a) (quote b) (quote c)]))))
(defn test-basic-quoting []

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -110,7 +110,7 @@
((wraps func)
(fn [&rest args &kwargs kwargs]
(func #* (map inc args)
#** (dict-comp k (inc v) [[k v] (.items kwargs)])))))
#** (dfor [k v] (.items kwargs) [k (inc v)])))))
#@(increment-arguments
(defn foo [&rest args &kwargs kwargs]

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
@ -27,7 +27,7 @@
cls)
(with-decorator bardec
(defclass cls []
[attr1 123]))
(setv attr1 123)))
(assert (= cls.attr1 123))
(assert (= cls.attr2 456)))

View File

@ -1,4 +1,4 @@
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.

View File

@ -1,5 +1,5 @@
#!/usr/bin/env hy
;; Copyright 2018 the authors.
;; Copyright 2019 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.

View File

@ -0,0 +1,2 @@
(print "This is a __main__.hy")
(setv visited_main True)

View File

@ -0,0 +1,8 @@
(defmacro bar [expr]
`(print ~expr))
(defmacro foo [expr]
`(do (require [tests.resources.bin.circular-macro-require [bar]])
(bar ~expr)))
(foo 42)

View File

@ -0,0 +1,3 @@
(require [hy.extra.anaphoric [ap-if]])
(print (eval '(ap-if (+ "a" "b") (+ it "c"))))

5
tests/resources/fails.hy Normal file
View File

@ -0,0 +1,5 @@
"This module produces an error when imported."
(defmacro a-macro [x]
(+ x 1))
(print (a-macro 'blah))

View File

@ -0,0 +1 @@
(print "hello world")

View File

@ -0,0 +1,5 @@
(setv a 1)
(defn f []
(import circular)
circular.a)
(print (f))

Some files were not shown because too many files have changed in this diff Show More