Merge branch 'master' into style-update
This commit is contained in:
commit
1663e5ccf5
11
.travis.yml
11
.travis.yml
@ -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
|
||||
|
8
AUTHORS
8
AUTHORS
@ -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>
|
||||
|
2
LICENSE
2
LICENSE
@ -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
193
NEWS.rst
@ -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
|
||||
==============================
|
||||
|
40
README.md
40
README.md
@ -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))
|
||||
|
48
conftest.py
48
conftest.py
@ -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 path.basename != "__init__.hy"):
|
||||
|
||||
pytest_mod = pytest.Module(path, parent)
|
||||
return pytest_mod
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
@ -12,7 +12,6 @@ Contents:
|
||||
:maxdepth: 3
|
||||
|
||||
loop
|
||||
multi
|
||||
profile
|
||||
sequences
|
||||
walk
|
||||
|
@ -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/
|
@ -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.
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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>`_
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
.. _hacking:
|
||||
|
||||
===============
|
||||
Hacking on Hy
|
||||
===============
|
||||
|
@ -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
|
||||
|
@ -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 target–value 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 target–value 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
|
||||
|
@ -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)
|
||||
|
@ -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``
|
||||
|
||||
|
@ -12,4 +12,5 @@ Contents:
|
||||
syntax
|
||||
api
|
||||
core
|
||||
model_patterns
|
||||
internals
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
113
docs/language/model_patterns.rst
Normal file
113
docs/language/model_patterns.rst
Normal 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
|
@ -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
|
||||
|
@ -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.
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
||||
Basic intro to Lisp for Pythonistas
|
||||
===================================
|
||||
|
||||
Okay, maybe you've never used Lisp before, but you've used Python!
|
||||
|
||||
A "hello world" program in Hy is actually super simple. Let's try it:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(print "hello world")
|
||||
|
||||
See? Easy! As you may have guessed, this is the same as the Python
|
||||
version of::
|
||||
|
||||
print("hello world")
|
||||
|
||||
To add up some super simple math, we could do:
|
||||
|
||||
.. code-block:: clj
|
||||
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 ``+``::
|
||||
|
||||
(+ 1 3)
|
||||
|
||||
Which would return 4 and would be the equivalent of:
|
||||
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``.
|
||||
|
||||
.. code-block:: clj
|
||||
Here's a more complex example::
|
||||
|
||||
1 + 3
|
||||
(- (* (+ 1 3 88) 2) 8)
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
.. code-block:: clj
|
||||
.. code-block:: python
|
||||
|
||||
(+ 1 3 55)
|
||||
((1 + 3 + 88) * 2) - 8
|
||||
|
||||
Which would return 59.
|
||||
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::
|
||||
|
||||
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:
|
||||
(- (* (+ 1 3 88) 2) 8)
|
||||
(- (* 92 2) 8)
|
||||
(- 184 8)
|
||||
176
|
||||
|
||||
.. code-block:: clj
|
||||
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.
|
||||
|
||||
(setv result (- (/ (+ 1 3 88) 2) 8))
|
||||
Comments start with a ``;`` character and continue till the end of the line. A
|
||||
comment is functionally equivalent to whitespace. ::
|
||||
|
||||
This would return 38.0 But why? Well, we could look at the equivalent
|
||||
expression in python::
|
||||
(print (** 2 64)) ; Max 64-bit unsigned integer value
|
||||
|
||||
result = ((1 + 3 + 88) / 2) - 8
|
||||
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::
|
||||
|
||||
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::
|
||||
#!/usr/bin/env hy
|
||||
(print "Make me executable, and run me!")
|
||||
|
||||
result = ((1 + 3 + 88) / 2) - 8
|
||||
# simplified to...
|
||||
result = (92 / 2) - 8
|
||||
# simplified to...
|
||||
result = 46.0 - 8
|
||||
# simplified to...
|
||||
result = 38.0
|
||||
Literals
|
||||
========
|
||||
|
||||
Now let's try the same thing in Hy:
|
||||
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.
|
||||
|
||||
.. code-block:: clj
|
||||
============== ================ =================
|
||||
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`
|
||||
============== ================ =================
|
||||
|
||||
(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)
|
||||
In addition, Hy has a Clojure-style literal syntax for
|
||||
:class:`fractions.Fraction`: ``1/3`` is equivalent to ``fractions.Fraction(1,
|
||||
3)``.
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(if (try-some-thing)
|
||||
(do
|
||||
(print "this is if true")
|
||||
(print "and why not, let's keep talking about how true it is!"))
|
||||
(print "this one's still simply just false"))
|
||||
|
||||
You can see that we used ``do`` to wrap multiple statements. If you're
|
||||
familiar with other Lisps, this is the equivalent of ``progn``
|
||||
elsewhere.
|
||||
|
||||
Comments start with semicolons:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(print "this will run")
|
||||
; (print "but this will not")
|
||||
(+ 1 2 3) ; we'll execute the addition, but not this comment!
|
||||
|
||||
Hashbang (``#!``) syntax is supported:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
#! /usr/bin/env hy
|
||||
(print "Make me executable, and run me!")
|
||||
|
||||
Looping is not hard but has a kind of special structure. In Python,
|
||||
we might do::
|
||||
|
||||
for i in range(10):
|
||||
print("'i' is now at " + str(i))
|
||||
|
||||
The equivalent in Hy would be:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(for [i (range 10)]
|
||||
(print (+ "'i' is now at " (str i))))
|
||||
(print (lfor x [1 2 3] (* x 2))) ; => [2, 4, 6]
|
||||
|
||||
|
||||
You can also import and make use of various Python libraries. For
|
||||
example:
|
||||
Functions, classes, and modules
|
||||
===============================
|
||||
|
||||
.. code-block:: clj
|
||||
Define named functions with :ref:`defn`::
|
||||
|
||||
(import os)
|
||||
(defn fib [n]
|
||||
(if (< n 2)
|
||||
n
|
||||
(+ (fib (- n 1)) (fib (- n 2)))))
|
||||
(print (fib 8)) ; => 21
|
||||
|
||||
(if (os.path.isdir "/tmp/somedir")
|
||||
(os.mkdir "/tmp/somedir/anotherdir")
|
||||
(print "Hey, that path isn't there!"))
|
||||
Define anonymous functions with :ref:`fn`::
|
||||
|
||||
Python's context managers (``with`` statements) are used like this:
|
||||
(print (list (filter (fn [x] (% x 2)) (range 10))))
|
||||
; => [1, 3, 5, 7, 9]
|
||||
|
||||
.. code-block:: clj
|
||||
Special symbols in the parameter list of ``defn`` or ``fn`` allow you to
|
||||
indicate optional arguments, provide default values, and collect unlisted
|
||||
arguments::
|
||||
|
||||
(with [f (open "/tmp/data.in")]
|
||||
(print (.read f)))
|
||||
(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)]
|
||||
|
||||
which is equivalent to::
|
||||
Set a function parameter by name with a ``:keyword``::
|
||||
|
||||
with open("/tmp/data.in") as f:
|
||||
print(f.read())
|
||||
(test 1 2 :d "y") ; => [1, 2, None, 'y', ()]
|
||||
|
||||
And yes, we do have List comprehensions! In Python you might do::
|
||||
Define classes with :ref:`defclass`::
|
||||
|
||||
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]
|
||||
(defclass FooBar []
|
||||
(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:
|
||||
Here we create a new instance ``fb`` of ``FooBar`` and access its attributes by
|
||||
various means::
|
||||
|
||||
.. code-block:: clj
|
||||
(setv fb (FooBar 15))
|
||||
(print fb.x) ; => 15
|
||||
(print (. fb x)) ; => 15
|
||||
(print (.get-x fb)) ; => 15
|
||||
(print (fb.get-x)) ; => 15
|
||||
|
||||
(setv bar (FooBar 1))
|
||||
(print (bar.get-x))
|
||||
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)``.
|
||||
|
||||
Or using the leading dot syntax!
|
||||
Access an external module, whether written in Python or Hy, with
|
||||
:ref:`import`::
|
||||
|
||||
.. code-block:: clj
|
||||
(import math)
|
||||
(print (math.sqrt 2)) ; => 1.4142135623730951
|
||||
|
||||
(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
142
docs/whyhy.rst
Normal 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
112
fastentrypoints.py
Normal 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)
|
@ -5,7 +5,10 @@ import os, subprocess, runpy
|
||||
os.chdir(os.path.split(os.path.abspath(__file__))[0])
|
||||
VERSIONFILE = os.path.join("hy", "version.py")
|
||||
|
||||
try:
|
||||
if "HY_VERSION" in os.environ:
|
||||
__version__ = os.environ["HY_VERSION"]
|
||||
else:
|
||||
try:
|
||||
__version__ = (subprocess.check_output
|
||||
(["git", "describe", "--tags", "--dirty"])
|
||||
.decode('ASCII').strip()
|
||||
@ -13,7 +16,7 @@ try:
|
||||
with open(VERSIONFILE, "wt") as o:
|
||||
o.write("__version__ = {!r}\n".format(__version__))
|
||||
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
if os.path.exists(VERSIONFILE):
|
||||
__version__ = runpy.run_path(VERSIONFILE)['__version__']
|
||||
else:
|
||||
|
@ -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
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
548
hy/cmdline.py
548
hy/cmdline.py
@ -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:
|
||||
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
|
||||
|
||||
try:
|
||||
def ast_callback(main_ast, expr_ast):
|
||||
def ast_callback(self, exec_ast, eval_ast):
|
||||
if self.spy:
|
||||
try:
|
||||
# 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)])
|
||||
new_ast = ast.Module(
|
||||
exec_ast.body + [ast.Expr(eval_ast.body)],
|
||||
type_ignores=[])
|
||||
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
|
||||
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:
|
||||
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
|
||||
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.
|
||||
if self.print_last_value:
|
||||
try:
|
||||
output = self.output_fn(value)
|
||||
except Exception as e:
|
||||
error_handler(e)
|
||||
output = self.output_fn(self.last_value)
|
||||
except Exception:
|
||||
self.showtraceback()
|
||||
return False
|
||||
|
||||
print(output)
|
||||
return False
|
||||
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
tree = hy_parse(source, filename=filename)
|
||||
except HyLanguageError:
|
||||
hy_exc_handler(*sys.exc_info())
|
||||
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)
|
||||
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(
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
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))
|
||||
|
1689
hy/compiler.py
1689
hy/compiler.py
File diff suppressed because it is too large
Load Diff
@ -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:
|
||||
try:
|
||||
readline.write_history_file(history)
|
||||
except IOError:
|
||||
pass
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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)))))
|
@ -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)
|
||||
|
@ -1,18 +1,20 @@
|
||||
;; 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]
|
||||
|
||||
(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))
|
||||
--getitem-- (fn [self n]
|
||||
|
||||
(defn --getitem-- [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))])
|
||||
(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.
|
||||
@ -23,7 +25,8 @@
|
||||
(setv (. self high-water) (inc (. self high-water)))
|
||||
(.append (. self cache) (.func self (. self high-water))))
|
||||
(get self n))))))
|
||||
--iter-- (fn [self]
|
||||
|
||||
(defn --iter-- [self]
|
||||
"create iterator for this sequence"
|
||||
(setv index 0)
|
||||
(try (while True
|
||||
@ -31,7 +34,8 @@
|
||||
(setv index (inc index)))
|
||||
(except [IndexError]
|
||||
(return))))
|
||||
--len-- (fn [self]
|
||||
|
||||
(defn --len-- [self]
|
||||
"length of the sequence, dangerous for infinite sequences"
|
||||
(setv index (. self high-water))
|
||||
(try (while True
|
||||
@ -39,17 +43,20 @@
|
||||
(setv index (inc index)))
|
||||
(except [IndexError]
|
||||
(len (. self cache)))))
|
||||
max-items-in-repr 10
|
||||
--str-- (fn [self]
|
||||
|
||||
(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))))
|
||||
--repr-- (fn [self]
|
||||
|
||||
(defn --repr-- [self]
|
||||
"string representation of this sequence"
|
||||
(.--str-- self))])
|
||||
(.--str-- self)))
|
||||
|
||||
(defmacro seq [param &rest seq-code]
|
||||
`(Sequence (fn ~param (do ~@seq-code))))
|
||||
|
@ -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',]
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
(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))
|
||||
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])
|
||||
|
@ -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 ~@(reduce + (gfor
|
||||
branch branches
|
||||
(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))
|
||||
(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)))
|
||||
|
@ -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]
|
||||
(defn @ [a1 &rest a-rest]
|
||||
"Shadowed `@` operator matrix multiples `a1` by each `a-rest`."
|
||||
(reduce operator.matmul a-rest a1)))
|
||||
(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 '@))
|
||||
|
377
hy/errors.py
377
hy/errors.py
@ -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.
|
||||
|
||||
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:
|
||||
super(HyLanguageError, self).__init__(message)
|
||||
|
||||
def compute_lineinfo(self, expression, filename, source, lineno, colno):
|
||||
|
||||
# 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)
|
||||
|
||||
if self.text:
|
||||
lines = self.text.splitlines()
|
||||
|
||||
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)
|
||||
|
||||
# Trim the source down to the essentials.
|
||||
self.text = '\n'.join(lines[self.lineno-1:end_line])
|
||||
|
||||
if end_column:
|
||||
if self.lineno == end_line:
|
||||
self.arrow_offset = end_column
|
||||
else:
|
||||
self.arrow_offset = len(self.text[0])
|
||||
|
||||
self.arrow_offset -= self.offset
|
||||
else:
|
||||
self.arrow_offset = None
|
||||
else:
|
||||
# We could attempt to extract the source given a filename, but we
|
||||
# don't.
|
||||
self.lineno = lineno
|
||||
self.offset = colno
|
||||
self.arrow_offset = None
|
||||
|
||||
def __str__(self):
|
||||
if isinstance(self.exception, HyTypeError):
|
||||
return str(self.exception)
|
||||
if self.traceback:
|
||||
tb = "".join(traceback.format_tb(self.traceback)).strip()
|
||||
else:
|
||||
tb = "No traceback available. 😟"
|
||||
return("Internal Compiler Bug 😱\n⤷ %s: %s\nCompilation traceback:\n%s"
|
||||
% (self.exception.__class__.__name__,
|
||||
self.exception, tb))
|
||||
|
||||
|
||||
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
|
||||
|
||||
def __str__(self):
|
||||
|
||||
result = ""
|
||||
|
||||
if all(getattr(self.expression, x, None) is not None
|
||||
for x in ("start_line", "start_column", "end_column")):
|
||||
|
||||
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
|
||||
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) + '^')
|
||||
|
||||
else:
|
||||
result += ' File "%s", unknown location\n' % self.filename
|
||||
|
||||
result += colored.yellow("%s: %s\n\n" %
|
||||
(self.__class__.__name__,
|
||||
self.message))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class HyMacroExpansionError(HyTypeError):
|
||||
pass
|
||||
|
||||
|
||||
class HyIOError(HyError, IOError):
|
||||
"""Provide an exception message that includes SyntaxError-like source
|
||||
line information when available.
|
||||
"""
|
||||
Trivial subclass of IOError and HyError, to distinguish between
|
||||
IOErrors raised by Hy itself as opposed to Hy programs.
|
||||
# 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__()
|
||||
|
||||
# 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 HyCompileError(HyInternalError):
|
||||
"""Unexpected errors occurring within the compiler."""
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class HyMacroExpansionError(HyLanguageError):
|
||||
"""Errors caused by invalid use of Hy macros.
|
||||
|
||||
This, and any errors inheriting from this, are user-facing.
|
||||
"""
|
||||
|
||||
|
||||
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
|
||||
|
@ -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)))
|
||||
~@(gfor i (range 1 (-> (lfor a %symbols
|
||||
:if (.isdigit (cut a 1))
|
||||
(int (cut a 1)))
|
||||
(or (, 0))
|
||||
max
|
||||
inc))])
|
||||
inc))
|
||||
(HySymbol (+ "%" (str i))))
|
||||
;; generate the &rest parameter only if '%* is present in expr
|
||||
~@(if (in '%* %symbols)
|
||||
'(&rest %*))
|
||||
|
@ -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)))))))
|
||||
|
406
hy/importer.py
406
hy/importer.py
@ -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)
|
||||
|
||||
|
||||
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 = inspect.getmodule(None, _filename=loader.path)
|
||||
except KeyError:
|
||||
module = None
|
||||
|
||||
bytecode_path = get_bytecode_path(fpath)
|
||||
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
|
||||
|
||||
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
|
||||
yield module
|
||||
finally:
|
||||
if tmp_mod:
|
||||
del sys.modules[loader.name]
|
||||
|
||||
|
||||
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:
|
||||
if loader_type is None:
|
||||
loader = pkgutil.get_loader(modname)
|
||||
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))
|
||||
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:
|
||||
module = imp.load_compiled(module_name, bytecode_path)
|
||||
# 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')
|
||||
|
||||
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
|
||||
return (code, fname)
|
||||
|
||||
|
||||
def import_buffer_to_module(module_name, buf):
|
||||
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
|
||||
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))
|
||||
|
@ -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))
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
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]
|
||||
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:
|
||||
source_line = ""
|
||||
lineno = lineno or 1
|
||||
colno = colno or 1
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
202
hy/lex/parser.py
202
hy/lex/parser.py
@ -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()
|
||||
|
414
hy/macros.py
414
hy/macros.py
@ -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
|
||||
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,31 +318,30 @@ 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)
|
||||
|
||||
if isinstance(obj, HyExpression):
|
||||
obj.module = inspect.getmodule(m)
|
||||
|
||||
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 once:
|
||||
@ -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")
|
||||
|
@ -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))
|
||||
|
127
hy/models.py
127
hy/models.py
@ -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
7
issue_template.md
Normal 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.
|
@ -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")))
|
@ -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
|
||||
|
24
setup.py
24
setup.py
@ -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",
|
||||
|
@ -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,8 +121,7 @@ def test_ast_good_raise():
|
||||
can_compile("(raise e)")
|
||||
|
||||
|
||||
if PY3:
|
||||
def test_ast_raise_from():
|
||||
def test_ast_raise_from():
|
||||
can_compile("(raise Exception :from NameError)")
|
||||
|
||||
|
||||
@ -196,13 +204,13 @@ def test_ast_bad_global():
|
||||
cant_compile("(global (foo))")
|
||||
|
||||
|
||||
if PY3:
|
||||
def test_ast_good_nonlocal():
|
||||
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():
|
||||
|
||||
def test_ast_bad_nonlocal():
|
||||
"Make sure AST can't compile invalid nonlocal"
|
||||
cant_compile("(nonlocal)")
|
||||
cant_compile("(nonlocal (foo))")
|
||||
@ -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"
|
||||
|
||||
|
||||
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))")
|
||||
|
||||
|
||||
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")')
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
with tempfile.NamedTemporaryFile(suffix='.hy', delete=True) as f:
|
||||
f.write(b'(defn pyctest [s] (+ "X" s "Y"))')
|
||||
f.close()
|
||||
f.flush()
|
||||
|
||||
pyc_path = importlib.util.cache_from_source(f.name)
|
||||
|
||||
try:
|
||||
os.remove(get_bytecode_path(f.name))
|
||||
os.remove(pyc_path)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
import_file_to_module("mymodule", f.name)
|
||||
assert os.path.exists(get_bytecode_path(f.name))
|
||||
|
||||
os.remove(f.name)
|
||||
os.remove(get_bytecode_path(f.name))
|
||||
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)
|
||||
|
@ -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)
|
||||
with tempfile.NamedTemporaryFile(suffix='.hy') as f:
|
||||
f.write(b'(defn pyctest [s] (+ "X" s "Y"))')
|
||||
f.close()
|
||||
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)
|
||||
try:
|
||||
mod = hy.importer._import_from_path('pyc', cfile)
|
||||
finally:
|
||||
os.remove(cfile)
|
||||
|
||||
assert mod.pyctest('Foo') == 'XFooY'
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
224
tests/native_tests/comprehensions.hy
Normal file
224
tests/native_tests/comprehensions.hy
Normal 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"])))
|
@ -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)")))
|
||||
"(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")))
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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")))
|
@ -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.
|
||||
|
||||
|
@ -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 [b 2])
|
||||
`(with [c 3]
|
||||
~(with* [d 4] (do))
|
||||
~@[(with* [e 5] (do))]))))))
|
||||
~(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)))
|
||||
@ -122,7 +129,7 @@
|
||||
'(foo `(bar a ~a ~"x"))))
|
||||
(assert (= `(foo ~@[a])
|
||||
'(foo "x")))
|
||||
(assert (= `(foo `(bar [a] ~@[a] ~@~[a 'a `a] ~~@[a]))
|
||||
(assert (= `(foo `(bar [a] ~@[a] ~@~(HyList [a 'a `a]) ~~@[a]))
|
||||
'(foo `(bar [a] ~@[a] ~@["x" a a] ~"x"))))))
|
||||
|
||||
(defn test-let-except []
|
||||
@ -329,3 +336,12 @@
|
||||
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"))))
|
||||
|
@ -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))))
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
(except [e [SyntaxError]] (assert (in "illegal target for assignment" (str e)))))
|
||||
(try (eval '(setv False 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 '(setv True 0))
|
||||
(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 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))))))
|
||||
|
||||
|
||||
(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))
|
||||
(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"))))))))
|
||||
|
||||
|
||||
(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))))))
|
||||
|
@ -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"))
|
||||
|
@ -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))))
|
70
tests/native_tests/model_patterns.hy
Normal file
70
tests/native_tests/model_patterns.hy
Normal 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])))
|
@ -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))))
|
||||
|
@ -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)))
|
||||
|
@ -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))))))
|
@ -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")))
|
||||
|
29
tests/native_tests/py38_only_tests.hy
Normal file
29
tests/native_tests/py38_only_tests.hy
Normal 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))
|
@ -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"])))
|
@ -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 []
|
||||
|
@ -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]
|
||||
|
@ -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)))
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
2
tests/resources/bin/__main__.hy
Normal file
2
tests/resources/bin/__main__.hy
Normal file
@ -0,0 +1,2 @@
|
||||
(print "This is a __main__.hy")
|
||||
(setv visited_main True)
|
8
tests/resources/bin/circular_macro_require.hy
Normal file
8
tests/resources/bin/circular_macro_require.hy
Normal 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)
|
3
tests/resources/bin/require_and_eval.hy
Normal file
3
tests/resources/bin/require_and_eval.hy
Normal 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
5
tests/resources/fails.hy
Normal file
@ -0,0 +1,5 @@
|
||||
"This module produces an error when imported."
|
||||
(defmacro a-macro [x]
|
||||
(+ x 1))
|
||||
|
||||
(print (a-macro 'blah))
|
1
tests/resources/hello_world.hy
Normal file
1
tests/resources/hello_world.hy
Normal file
@ -0,0 +1 @@
|
||||
(print "hello world")
|
5
tests/resources/importer/circular.hy
Normal file
5
tests/resources/importer/circular.hy
Normal 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
Loading…
Reference in New Issue
Block a user