Merge pull request #1817 from Kodiologist/inline-python

Allow inline Python
This commit is contained in:
Kodi Arfer 2019-09-17 12:04:26 -04:00 committed by GitHub
commit 84d1a116f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 82 additions and 11 deletions

View File

@ -13,6 +13,8 @@ Removals
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.

View File

@ -1406,16 +1406,42 @@ parameter will be returned.
True
print
-----
.. _py-specialform:
``print`` is used to output on screen. Example usage:
py
--
.. code-block:: clj
``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 "Hello world!")
(print "A result from Python:" (py "'hello' + 'world'"))
.. note:: ``print`` always returns ``None``.
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:

View File

@ -19,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)

View File

@ -81,8 +81,8 @@ 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 <interop>`, which
can be a good way to get your feet wet in Hy.
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

View File

@ -20,6 +20,7 @@ from hy.macros import require, load_macros, macroexpand, tag_macroexpand
import hy.core
import re
import textwrap
import pkgutil
import traceback
import importlib
@ -1589,6 +1590,22 @@ class HyASTCompiler(object):
if ast_str(root) == "eval_and_compile"
else Result())
@special(["py", "pys"], [STR])
def compile_inline_python(self, expr, root, code):
exec_mode = root == HySymbol("pys")
try:
o = ast.parse(
textwrap.dedent(code) if exec_mode else code,
self.filename,
'exec' if exec_mode else 'eval').body
except (SyntaxError, ValueError if PY36 else TypeError) as e:
raise self._syntax_error(
expr,
"Python parse error in '{}': {}".format(root, e))
return Result(stmts=o) if exec_mode else o
@builds_model(HyExpression)
def compile_expression(self, expr):
# Perform macro expansions

View File

@ -639,3 +639,10 @@ def test_futures_imports():
assert hy_ast.body[0].module == '__future__'
assert hy_ast.body[1].module == 'hy.core.language'
def test_inline_python():
can_compile('(py "1 + 1")')
cant_compile('(py "1 +")')
can_compile('(pys "if 1:\n 2")')
cant_compile('(pys "if 1\n 2")')

View File

@ -156,3 +156,14 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
(with [c1 (closing (Closeable)) c2 (closing (Closeable))]
(setv c1.x "v1")
(setv c2.x "v2"))
(setv closed1 (.copy closed))
(pys "
closed = []
pys_accum = []
for i in range(5):
with closing(Closeable()) as o:
class C: pass
o.x = C()
pys_accum.append(i)")
(setv py-accum (py "''.join(map(str, pys_accum))"))

View File

@ -120,4 +120,10 @@ def assert_stuff(m):
assert issubclass(m.C2, m.C1)
assert (m.C2.attr1, m.C2.attr2) == (5, 6)
assert m.closed == ["v2", "v1"]
assert m.closed1 == ["v2", "v1"]
assert len(m.closed) == 5
for a, b in itertools.combinations(m.closed, 2):
assert type(a) is not type(b)
assert m.pys_accum == [0, 1, 2, 3, 4]
assert m.py_accum == "01234"